Skip to content

Commit b33d8a5

Browse files
msyycCopilotCopilotiscai-msft
authored
Run optional post-emitter.ps1 script after SDK generation (#47456)
* Run optional post-emitter.ps1 script after SDK generation Add support for a hard-coded, optional post-emitter PowerShell script (post-emitter.ps1) located in the generated package folder (sdk/<service>/azure-*). When present, it is executed after code generation so service teams (e.g. Foundry) can run custom post-processing on the generated SDK. Safety/robustness: - Only runs a script located directly inside the package folder (guards against path traversal / symlinks). - Invoked non-interactively (-NonInteractive -NoProfile). - Captures and logs stdout/stderr so output is visible in the pipeline. - Wrapped with a 600s timeout. - Failures are logged but never fail the overall generation. - Gracefully skips when no PowerShell (pwsh) executable is available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Log post-emitter failures/timeout as warning instead of error Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Simplify post-emitter script check to existence only Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename post-emitter script to _post_emitter.ps1 to signal it is private Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: rename post emitter script to PostEmitter.ps1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Log a message when post-emitter script is not found Helps debugging when a service team adds the script but does not see it run. Also fix a stale docstring reference to the script name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Delete log.txt * Reference POST_EMITTER_SCRIPT_NAME in docstring instead of hard-coded filename Avoids the docstring drifting out of sync with the constant (the previous literal was stale). The constant is now the single source of truth for the script name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: iscai-msft <isabellavcai@gmail.com>
1 parent b5aae50 commit b33d8a5

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

eng/tools/azure-sdk-tools/packaging_tools/sdk_generator.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,72 @@ def execute_func_with_timeout(func, timeout: int = 900) -> Any:
5151
return multiprocessing.Pool(processes=1).apply_async(func).get(timeout)
5252

5353

54+
# Hard-coded name of the optional post-emitter script. If a service team places a
55+
# script with this name in the generated package folder (sdk/<service>/azure-*),
56+
# it will be executed after code generation.
57+
POST_EMITTER_SCRIPT_NAME = "PostEmitter.ps1"
58+
59+
60+
def run_post_emitter_script(sdk_code_path: str) -> None:
61+
"""Run the optional post-emitter PowerShell script for a package, if present.
62+
63+
When a script whose name matches ``POST_EMITTER_SCRIPT_NAME`` exists directly
64+
inside the generated package folder (``sdk/<service>/azure-*``), it is executed
65+
after code generation so service teams can run custom post-processing on the
66+
generated SDK. The script's stdout/stderr are captured and logged so they appear
67+
in the pipeline output. Failures are logged but never fail the overall generation.
68+
"""
69+
package_folder = Path(sdk_code_path).resolve()
70+
script_path = package_folder / POST_EMITTER_SCRIPT_NAME
71+
72+
# Run the script only when it exists; otherwise this is a no-op.
73+
if not script_path.is_file():
74+
_LOGGER.info(f"[POST-EMITTER] Skip running post-emitter script since file {script_path} was not found.")
75+
return
76+
77+
pwsh = shutil.which("pwsh") or shutil.which("powershell")
78+
if not pwsh:
79+
_LOGGER.warning(
80+
f"[POST-EMITTER] Found {script_path} but no PowerShell executable (pwsh/powershell) is available; skipping."
81+
)
82+
return
83+
84+
_LOGGER.info(f"[POST-EMITTER] Running post-emitter script: {script_path}")
85+
post_emitter_start_time = time.time()
86+
try:
87+
process = subprocess.run(
88+
[
89+
pwsh,
90+
"-NonInteractive",
91+
"-NoProfile",
92+
"-ExecutionPolicy",
93+
"Bypass",
94+
"-File",
95+
str(script_path),
96+
],
97+
cwd=str(package_folder),
98+
capture_output=True,
99+
text=True,
100+
timeout=600,
101+
)
102+
if process.stdout:
103+
_LOGGER.info(f"[POST-EMITTER] stdout:\n{process.stdout}")
104+
if process.stderr:
105+
_LOGGER.warning(f"[POST-EMITTER] stderr:\n{process.stderr}")
106+
if process.returncode != 0:
107+
_LOGGER.warning(f"[POST-EMITTER] Script {script_path} exited with non-zero code {process.returncode}.")
108+
else:
109+
_LOGGER.info(f"[POST-EMITTER] Script {script_path} completed successfully.")
110+
except subprocess.TimeoutExpired:
111+
_LOGGER.warning(f"[POST-EMITTER] Script {script_path} timed out after 600 seconds.")
112+
except Exception as e:
113+
_LOGGER.warning(f"[POST-EMITTER] Fail to run script {script_path}: {str(e)}")
114+
finally:
115+
_LOGGER.info(
116+
f"[POST-EMITTER] post-emitter script cost time: {int(time.time() - post_emitter_start_time)} seconds"
117+
)
118+
119+
54120
# return relative path like: network/azure-mgmt-network
55121
def extract_sdk_folder(python_md: List[str]) -> str:
56122
pattern = ["$(python-sdks-folder)", "azure-mgmt-"]
@@ -242,6 +308,9 @@ def main(generate_input, generate_output):
242308
readme_or_tsp=readme_or_tsp,
243309
)
244310

311+
# Run optional post-emitter script provided by the service team
312+
run_post_emitter_script(sdk_code_path)
313+
245314
# Generate ApiView
246315
if data.get("runMode") in ["spec-pull-request"]:
247316
apiview_start_time = time.time()

0 commit comments

Comments
 (0)