Skip to content

[build](feat) Added build entry setup_ascend.py - #1485

Open
LH-123L wants to merge 1 commit into
triton-lang:mainfrom
LH-123L:main-build
Open

[build](feat) Added build entry setup_ascend.py#1485
LH-123L wants to merge 1 commit into
triton-lang:mainfrom
LH-123L:main-build

Conversation

@LH-123L

@LH-123L LH-123L commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Triton Ascend Build System: Non-Intrusive Refactoring Design

1. Background and Goals

When maintaining the triton-ascend project, Ascend NPU-specific logic needs to
be injected into the upstream Triton build flow (backend registration, LLVM
build, patch application, coverage tooling, distributed submodule, wheel
repair, etc.). These changes were previously written directly into setup.py
in an intrusive manner, which caused:

  1. Tight coupling with upstream code, leading to frequent conflicts when
    rebasing or upgrading the upstream version;
  2. Ascend-specific logic scattered throughout setup.py, making it hard to
    maintain and review;
  3. No clear separation between the "upstream standard build" and the "Ascend
    build" paths.

Refactoring goals:

  • Do not delete or break the upstream implementation in setup.py;
  • Consolidate all Ascend intrusive changes into a standalone
    setup_ascend.py;
  • Keep the invocation compatible: python setup.py ... runs the upstream
    standard build, while python setup_ascend.py ... runs the Ascend build;
  • The dev patch is applied only in development scenarios and is skipped for
    releases.

2. Final File Layout

File Role Contains Ascend changes
setup.py Upstream standard build entry; identical to the upstream setup.py No
setup_ascend.py Ascend build entry, hosting all Ascend intrusive changes Yes

3. Usage

# Upstream standard build (pip's default entry, clean and non-intrusive)
python setup.py bdist_wheel

# Ascend build (injects all Ascend logic)
python setup_ascend.py bdist_wheel

# Development mode (explicitly enables the dev patch)
TRITON_DEV_MODE=1 python setup_ascend.py bdist_wheel

# Release mode (explicitly disables the dev patch)
TRITON_RELEASE_MODE=1 python setup_ascend.py bdist_wheel

4. Core Design of setup_ascend.py

4.1 Overall Flow

main() in setup_ascend.py uses a "intercept setup() call -> apply patches
-> re-invoke" monkey-patch pattern:

main()
 |- _set_default_env_vars()          # Set Ascend default env vars
 |- _ensure_distributed_submodule()  # Initialize distributed submodule if needed
 |- Temporarily replace setuptools.setup with a capturing function
 |- Dynamically load setup.py via importlib (its setup() is intercepted, not executed)
 |- Restore setuptools.setup
 |- _patch_module(mod)               # Apply all Ascend overrides to the upstream module
 |- _build_setup_kwargs(mod, kwargs) # Rewrite setup() arguments
 |- _real_setup(**kwargs)            # Actually perform the build

Key code (setup_ascend.py:513):

def main():
    _set_default_env_vars()
    _ensure_distributed_submodule()

    import setuptools
    _real_setup = setuptools.setup
    captured = {}

    def _capture_setup(**kwargs):
        captured["kwargs"] = kwargs

    setuptools.setup = _capture_setup
    try:
        spec = importlib.util.spec_from_file_location(
            "setup_triton", str(_TRITON_SETUP))
        mod = importlib.util.module_from_spec(spec)
        sys.modules["setup_triton"] = mod
        spec.loader.exec_module(mod)
    finally:
        setuptools.setup = _real_setup

    _patch_module(mod)
    kwargs = _build_setup_kwargs(mod, captured["kwargs"])
    _real_setup(**kwargs)

Why intercept setuptools.setup?
By temporarily replacing setuptools.setup during import, the upstream module
can fully define all its functions and classes, while the arguments passed to
setup() are captured. After we apply our patches, we invoke the real
setup() with the modified arguments.

4.2 Default Environment Variables (_set_default_env_vars)

Variable Default Description
TRITON_BUILD_WITH_CCACHE true Enable ccache acceleration
TRITON_BUILD_WITH_CLANG_LLD true Use clang/lld for linking
TRITON_BUILD_PROTON OFF Ascend does not build the proton profiler
TRITON_WHEEL_NAME triton-ascend Wheel package name
TRITON_BUILD_DISTRIBUTED OFF Whether to build Triton Distributed
TRITON_APPEND_CMAKE_ARGS -DTRITON_BUILD_UT=OFF Disable unit test builds

setdefault is used, so users can override these from the outside.

4.3 Dev / Release Mode Detection (_is_dev_mode)

Detection priority:

  1. TRITON_DEV_MODE=1 -> force development mode (apply the dev patch);
  2. TRITON_RELEASE_MODE=1 -> force release mode (skip the dev patch);
  3. Not a git repository -> release mode;
  4. Inside a git repository, if the current branch name starts with release
    -> release mode; otherwise development mode.
def _is_dev_mode():
    if os.environ.get("TRITON_DEV_MODE", "0").lower() in ("1", "on", "true"):
        return True
    if os.environ.get("TRITON_RELEASE_MODE", "0").lower() in ("1", "on", "true"):
        return False
    if not _is_git_repo():
        return False
    branch = subprocess.check_output(
        ["git", "rev-parse", "--abbrev-ref", "HEAD"], ...)
    return not branch.startswith("release")

4.4 Patch Application (_apply_triton_ascend_patch)

  • Always apply third_party/ascend/patch/triton-ascend-3.6.0.patch;
  • Only in dev mode, additionally apply
    triton-ascend-dev-3.6.0.patch (which touches
    python/triton/runtime/autotuner.py);
  • Before applying each patch, run git checkout -- on the affected files to
    avoid failures caused by applying a patch twice.

4.5 LLVM Package Info Override (get_llvm_package_info)

Override the upstream LLVM download source to point to the Huawei Cloud
Ascend-customized LLVM build:

https://triton-ascend-artifacts.obs.myhuaweicloud.com/llvm-builds/
    llvm-{rev}-{llvm_patch_hash}-{system_suffix}.tar.gz

Here llvm_patch_hash is the first 8 hex digits of the SHA256 of the contents
of third_party/ascend/patch/llvm_patch_*.patch. When the patches change, a
new package is automatically fetched. If there is no corresponding package for
the current platform (e.g. Windows), it falls back to the original upstream
implementation.

4.6 CMakeBuild Override

Inherit from the upstream CMakeBuild and override two methods:

run():

  1. Call the upstream download_and_copy_dependencies();
  2. Apply the Ascend patches;
  3. Verify CMake >= 3.20;
  4. If TRITON_ENABLE_COVERAGE_HITEST=1, configure the hitest coverage
    environment variables and append -DTRITON_ENABLE_COVERAGE_HITEST=ON to
    TRITON_APPEND_CMAKE_ARGS; otherwise clean up any residual hitest
    environment variables;
  5. Iterate over extensions and run build_extension.

build_extension():
By temporarily wrapping subprocess.check_call, Ascend-specific arguments are
injected during the cmake configuration phase (when cmd[0] == "cmake"
and --build is not present):

  • -DASCENDNPU_IR_TAG=... (if set)
  • -DLLVM_MAJOR_VERSION_22_COMPATIBLE=ON
  • -DTRITON_BUILD_DISTRIBUTED=ON/OFF

After the build completes, _copy_ascend_tools() is called to copy
triton-mlir-opt and triton-opt into the output directory and strip them
(non-Windows).

Wrapping subprocess.check_call rather than rewriting the whole
build_extension maximizes reuse of the upstream cmake argument assembly
logic and minimizes the diff against upstream.

4.7 BuildWheel (auditwheel Repair)

Override bdist_wheel:

  • Before building, call add_links(external_only=True);
  • When IS_MANYLINUX=TRUE, call auditwheel repair after building, tagging
    the wheel with manylinux_2_27_* and manylinux_2_28_*, and delete the
    original wheel.

4.8 Distributed Submodule Support

Three upstream functions are wrapped to include the triton_dist package
(controlled by TRITON_BUILD_DISTRIBUTED, default OFF):

  • get_package_dirs(): append the package path mapping for triton_dist;
  • get_packages(): walk Triton-distributed-ascend/python/triton_dist to
    automatically discover all subpackages;
  • add_links(): create a symlink for python/triton_dist.

_ensure_distributed_submodule() ensures the git submodule is initialized
before the build, and raises a clear error if it is missing.

4.9 setup() Argument Rewrite (_build_setup_kwargs)

On top of the captured upstream kwargs, the following fields are overridden:

Field Ascend value
name triton-ascend (controlled by TRITON_WHEEL_NAME)
version contents of version.txt + wheel suffix + git commit hash (non-manylinux)
url https://gitcode.com/Ascend/triton-ascend/
long_description repository README.md
install_requires fixed Ascend dependencies (attrs/numpy/scipy/pybind11/pandas, etc.) + triton==3.6.0 appended per architecture
cmdclass bdist_wheel->BuildWheel, build_ext->CMakeBuild
packages / package_dir re-invoked after patching to ensure the ascend backend and triton_dist are included
entry_points recomputed to ensure the ascend entry is present under triton.backends
package_data include *.py / *.pyi for triton_dist

Note: packages/package_dir/entry_points must be recomputed after
_patch_module, because the upstream module computes these values at load
time, before ascend is added to the backends list.

5. Ascend Backend Registration

First lines of _patch_module:

ascend_backend = mod.BackendInstaller.prepare("ascend")
mod.backends = [ascend_backend, *mod.backends]

BackendInstaller.prepare("ascend") scans third_party/ascend/backend/ and
adds it as an in-tree backend (is_external=False). As a result, the upstream
cmake configuration automatically includes ascend in
-DTRITON_CODEGEN_BACKENDS=ascend;nvidia;amd, and the triton.backends
entry point registers ascend = triton.backends.ascend.

6. Coverage Tooling (hitest)

Takes effect only when TRITON_ENABLE_COVERAGE_HITEST=1:

  • Inject a set of HITEST_* / HitestHome / lltcovRootpath environment
    variables;
  • Add the hitest directory to PATH and LD_LIBRARY_PATH;
  • Pass -DTRITON_ENABLE_COVERAGE_HITEST=ON via TRITON_APPEND_CMAKE_ARGS;
  • When disabled, call _clean_hitest_env() to remove all related variables so
    the environment is not polluted.

Default key paths:

  • HITEST_HOME=/opt/hitest/linux_avatar_x86_64
  • LLTCOV_ROOTPATH=/opt/covdata

7. Upgrade and Maintenance Strategy

When the upstream version is upgraded:

  1. Replace setup.py with the new upstream setup.py;
  2. Run python setup_ascend.py bdist_wheel and, based on any errors, adjust
    the patch points in setup_ascend.py (e.g. changed class/function names);
  3. Update the patch files under third_party/ascend/patch/ to adapt to the
    new upstream code;
  4. If the LLVM patches change, llvm_patch_hash changes automatically, so
    there is no need to manually bump the version.

Because all Ascend logic is isolated in setup_ascend.py with zero overlap
with upstream files, the conflict surface during upgrades is minimized.

8. Verification

python -m py_compile setup.py setup_ascend.py   # syntax check
python -c "import setup_ascend"                 # import check

All files pass the syntax check; setup_ascend can be imported normally and
the main function is callable.

New contributor declaration

  • I am not making a trivial change, such as fixing a typo in a comment.

  • I have written a PR description following these
    rules.

  • I have run pre-commit run --from-ref origin/main --to-ref HEAD.

  • Select one of the following.

    • I have added tests.
      • /test for lit tests
      • /unittest for C++ tests
      • /python/test for end-to-end tests
    • This PR does not need a test because FILL THIS IN.
  • Select one of the following.

    • I have not added any lit tests.
    • The lit tests I have added follow these best practices,
      including the "tests should be minimal" section. (Usually running Python code
      and using the instructions it generates is not minimal.)

@github-actions github-actions Bot added restricted-files Changes include files outside the repository-construction allowlist. documentation Improvements or additions to documentation CICD Issue about CICD pipelines. build python Changes to Python runtime or bindings labels Aug 11, 2026
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🔍 OpenCodeReview found 2 issue(s) in this PR.

  • ✅ Successfully posted inline: 2 comment(s)

@LH-123L

LH-123L commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

/retry

Comment thread setup_ascend.py
elif arch == "arm64":
system_suffix = "ubuntu-arm64"
elif arch == "x64":
vglibc = tuple(map(int, platform.libc_ver()[1].split(".")))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[bug · high]
On systems without glibc (e.g., musl-based Alpine Linux), platform.libc_ver() may return an empty version string ('', ''). Calling ''.split('.') returns [''] and int('') raises a ValueError, aborting the entire build. This function should handle the empty-string case gracefully and return a sensible fallback or None.

Suggestion:

Suggested change
vglibc = tuple(map(int, platform.libc_ver()[1].split(".")))
libc_ver = platform.libc_ver()[1]
if not libc_ver:
return None
vglibc = tuple(map(int, libc_ver.split(".")))

Comment thread setup_ascend.py
Comment on lines +107 to +111
def _is_dev_mode():
if os.environ.get("TRITON_WHEEL_VERSION_SUFFIX", ""):
return True
if "dev" in _get_default_version():
return True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[other · low]
When neither condition is met, _is_dev_mode() implicitly returns None instead of False. While None is falsy and works in boolean contexts, the implicit return is unclear. Adding an explicit return False improves readability and makes the function contract unambiguous.

Suggestion:

Suggested change
def _is_dev_mode():
if os.environ.get("TRITON_WHEEL_VERSION_SUFFIX", ""):
return True
if "dev" in _get_default_version():
return True
def _is_dev_mode():
if os.environ.get("TRITON_WHEEL_VERSION_SUFFIX", ""):
return True
if "dev" in _get_default_version():
return True
return False

Comment thread setup_ascend.py
Comment on lines +339 to +343
try:
out = subprocess.check_output(["cmake", "--version"])
except OSError:
raise RuntimeError("CMake must be installed to build the following extensions: " +
", ".join(e.name for e in self.extensions))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[bug · medium]
If the cmake binary exists but returns a non-zero exit code (e.g., corrupted installation), subprocess.check_output raises subprocess.CalledProcessError, which is not caught by the except OSError handler. This results in an unhandled exception rather than a helpful error message. Catch subprocess.CalledProcessError as well.

Suggestion:

Suggested change
try:
out = subprocess.check_output(["cmake", "--version"])
except OSError:
raise RuntimeError("CMake must be installed to build the following extensions: " +
", ".join(e.name for e in self.extensions))
try:
out = subprocess.check_output(["cmake", "--version"])
except (OSError, subprocess.CalledProcessError):
raise RuntimeError("CMake must be installed to build the following extensions: " +
", ".join(e.name for e in self.extensions))

Comment thread setup_ascend.py
bdist_wheel.run(self)

if is_manylinux:
file = glob.glob(os.path.join(self.dist_dir, "*-linux_*.whl"))[0]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[bug · medium]
glob.glob may return an empty list if no wheel file matches the pattern (e.g., if the wheel uses a different platform tag), causing an IndexError that crashes the build with an unhelpful message. Add a guard to check the result before indexing, and raise a meaningful error if no matching wheel is found.

Suggestion:

Suggested change
file = glob.glob(os.path.join(self.dist_dir, "*-linux_*.whl"))[0]
files = glob.glob(os.path.join(self.dist_dir, "*-linux_*.whl"))
if not files:
raise RuntimeError("No wheel file matching '*-linux_*.whl' found in dist_dir")
file = files[0]

Comment on lines 80 to +81
cd base
MAX_JOBS="$NUM_PROCS" python setup.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"
MAX_JOBS="$NUM_PROCS" python setup_ascend.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[bug · critical]
Critical bug: setup_ascend.py does not exist in the base branch checkout.

The "Checkout base" step (line 53) checks out the base/target branch via ref: ${{ github.base_ref }}. Since setup_ascend.py is a new file added by this PR (listed as ADDED in the changed files), it does not exist in the base branch. The build step cd base && python setup_ascend.py bdist_wheel ... will fail with a FileNotFoundError.

Comparison with other workflow fixes:

The other modified workflow Ascend950-ci.yml wisely uses a conditional check:

if [ -f setup_ascend.py ]; then
  SETUP_PY="setup_ascend.py"
elif [ -f python/setup.py ]; then
  SETUP_PY="setup.py"
else
  echo "ERROR: setup.py or setup_ascend.py not found" >&2
  exit 1
fi

Suggestion: Apply the same pattern here. For the base branch build, fall back to setup.py since setup_ascend.py won't be there. Alternatively, only use setup_ascend.py for the PR checkout (which contains the new file) and keep using setup.py for the base checkout.

Comment on lines 80 to +81
cd base
MAX_JOBS="$NUM_PROCS" python setup.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"
MAX_JOBS="$NUM_PROCS" python setup_ascend.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[bug · critical]
Critical: setup_ascend.py does not exist in the base branch checkout, causing the build to fail.

The "Checkout base" step (line 53) checks out the base/target branch at ref: ${{ github.base_ref }}. Since setup_ascend.py is newly added in this PR (it's an ADDED file), it does not exist in the base branch checkout. Running python setup_ascend.py bdist_wheel inside the base/ directory will fail with FileNotFoundError.

Meanwhile, the base branch's setup.py still contains all the Ascend-specific build logic that hasn't yet been extracted (the extraction happens only in this PR), so the base build should continue using setup.py.

Fix: Revert this line back to python setup.py bdist_wheel, since setup.py is the correct entry point for the base branch. Only the PR checkout should use setup_ascend.py.

Comment on lines 100 to +101
cd pr
MAX_JOBS="$NUM_PROCS" python setup.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/pr/wheelhouse"
MAX_JOBS="$NUM_PROCS" python setup_ascend.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/pr/wheelhouse"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[bug · low]
Same critical issue applies to the PR build step — but this one will work since the PR checkout does contain setup_ascend.py.

However, for consistency and to avoid future breakage (if the file name changes again), consider adopting the same defensive -f check pattern as Ascend950-ci.yml here as well.

Comment on lines 100 to +101
cd pr
MAX_JOBS="$NUM_PROCS" python setup.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/pr/wheelhouse"
MAX_JOBS="$NUM_PROCS" python setup_ascend.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/pr/wheelhouse"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[bug · low]
The PR build step change to setup_ascend.py is correct.

The PR checkout contains the newly added setup_ascend.py, and the modified setup.py no longer includes the Ascend-specific build logic (patches, coverage, distributed submodule, etc.). So using python setup_ascend.py for the PR build is the right change.

However, for defensive programming and consistency with Ascend950-ci.yml, consider adding a fallback check (e.g., if [ -f setup_ascend.py ]; then ...) to handle any future structural changes gracefully.

Comment on lines 80 to +81
cd base
MAX_JOBS="$NUM_PROCS" python setup.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"
MAX_JOBS="$NUM_PROCS" python setup_ascend.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bug · high
setup_ascend.py may not exist on the base branch, causing build failure

The "Build triton-ascend base" step (line 81) runs in the base directory, which was checked out via ref: ${{ github.base_ref }} (the target branch of the PR). Since setup_ascend.py is a new file being introduced in this PR, it does not exist on the base branch. This will cause the step to fail with FileNotFoundError.

In contrast, the parallel change in Ascend950-ci.yml uses a robust fallback pattern that checks for setup_ascend.py first and falls back to setup.py if not found. DynamicCVPipeline-ci.yml should adopt a similar approach for consistency and reliability.

Suggested fix:
Either use a check-then-fallback approach (as done in Ascend950-ci.yml), or ensure the base build continues to use setup.py while only the PR build uses setup_ascend.py.

Suggestion:

Suggested change
cd base
MAX_JOBS="$NUM_PROCS" python setup.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"
MAX_JOBS="$NUM_PROCS" python setup_ascend.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"
cd base
if [ -f setup_ascend.py ]; then
SETUP_PY="setup_ascend.py"
elif [ -f setup.py ]; then
SETUP_PY="setup.py"
else
echo "ERROR: setup_ascend.py or setup.py not found" >&2
exit 1
fi
MAX_JOBS="$NUM_PROCS" python ${SETUP_PY} bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"

Comment on lines 80 to +81
cd base
MAX_JOBS="$NUM_PROCS" python setup.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"
MAX_JOBS="$NUM_PROCS" python setup_ascend.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bug · high
setup_ascend.py may not exist on the base branch, causing build failure

The "Build triton-ascend base" step (line 81) runs in the base directory, which was checked out via ref: ${{ github.base_ref }} (the target branch of the PR). Since setup_ascend.py is a new file being introduced in this PR, it does not exist on the base branch. This will cause the step to fail with FileNotFoundError.

In contrast, the parallel change in Ascend950-ci.yml uses a robust fallback pattern that checks for setup_ascend.py first and falls back to setup.py if not found. DynamicCVPipeline-ci.yml should adopt a similar approach for consistency and reliability.

Suggested fix:
Either use a check-then-fallback approach (as done in Ascend950-ci.yml), or ensure the base build continues to use setup.py while only the PR build uses setup_ascend.py.

Suggestion:

Suggested change
cd base
MAX_JOBS="$NUM_PROCS" python setup.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"
MAX_JOBS="$NUM_PROCS" python setup_ascend.py bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"
cd base
if [ -f setup_ascend.py ]; then
SETUP_PY="setup_ascend.py"
elif [ -f setup.py ]; then
SETUP_PY="setup.py"
else
echo "ERROR: setup_ascend.py or setup.py not found" >&2
exit 1
fi
MAX_JOBS="$NUM_PROCS" python ${SETUP_PY} bdist_wheel --dist-dir="${GITHUB_WORKSPACE}/base/wheelhouse"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build CICD Issue about CICD pipelines. documentation Improvements or additions to documentation python Changes to Python runtime or bindings restricted-files Changes include files outside the repository-construction allowlist.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant