Skip to content
69 changes: 69 additions & 0 deletions eng/tools/azure-sdk-tools/packaging_tools/sdk_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,72 @@ def execute_func_with_timeout(func, timeout: int = 900) -> Any:
return multiprocessing.Pool(processes=1).apply_async(func).get(timeout)


# Hard-coded name of the optional post-emitter script. If a service team places a
# script with this name in the generated package folder (sdk/<service>/azure-*),
# it will be executed after code generation.
POST_EMITTER_SCRIPT_NAME = "PostEmitter.ps1"


def run_post_emitter_script(sdk_code_path: str) -> None:
"""Run the optional post-emitter PowerShell script for a package, if present.

When a script whose name matches ``POST_EMITTER_SCRIPT_NAME`` exists directly
inside the generated package folder (``sdk/<service>/azure-*``), it is executed
after code generation so service teams can run custom post-processing on the
generated SDK. The script's stdout/stderr are captured and logged so they appear
in the pipeline output. Failures are logged but never fail the overall generation.
"""
package_folder = Path(sdk_code_path).resolve()
script_path = package_folder / POST_EMITTER_SCRIPT_NAME

# Run the script only when it exists; otherwise this is a no-op.
if not script_path.is_file():
_LOGGER.info(f"[POST-EMITTER] Skip running post-emitter script since file {script_path} was not found.")
return
Comment thread
msyyc marked this conversation as resolved.

pwsh = shutil.which("pwsh") or shutil.which("powershell")
if not pwsh:
_LOGGER.warning(
f"[POST-EMITTER] Found {script_path} but no PowerShell executable (pwsh/powershell) is available; skipping."
)
return

_LOGGER.info(f"[POST-EMITTER] Running post-emitter script: {script_path}")
post_emitter_start_time = time.time()
try:
process = subprocess.run(
[
pwsh,
"-NonInteractive",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
str(script_path),
],
cwd=str(package_folder),
capture_output=True,
text=True,
timeout=600,
)
if process.stdout:
_LOGGER.info(f"[POST-EMITTER] stdout:\n{process.stdout}")
if process.stderr:
_LOGGER.warning(f"[POST-EMITTER] stderr:\n{process.stderr}")
if process.returncode != 0:
_LOGGER.warning(f"[POST-EMITTER] Script {script_path} exited with non-zero code {process.returncode}.")
else:
_LOGGER.info(f"[POST-EMITTER] Script {script_path} completed successfully.")
except subprocess.TimeoutExpired:
_LOGGER.warning(f"[POST-EMITTER] Script {script_path} timed out after 600 seconds.")
except Exception as e:
_LOGGER.warning(f"[POST-EMITTER] Fail to run script {script_path}: {str(e)}")
finally:
_LOGGER.info(
f"[POST-EMITTER] post-emitter script cost time: {int(time.time() - post_emitter_start_time)} seconds"
)


# return relative path like: network/azure-mgmt-network
def extract_sdk_folder(python_md: List[str]) -> str:
pattern = ["$(python-sdks-folder)", "azure-mgmt-"]
Expand Down Expand Up @@ -242,6 +308,9 @@ def main(generate_input, generate_output):
readme_or_tsp=readme_or_tsp,
)

# Run optional post-emitter script provided by the service team
run_post_emitter_script(sdk_code_path)
Comment thread
msyyc marked this conversation as resolved.

# Generate ApiView
if data.get("runMode") in ["spec-pull-request"]:
apiview_start_time = time.time()
Expand Down
Loading