From 2bbe3ed9ccb6e8802242cb7d79feb7a370e0c638 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 16 May 2024 14:14:14 -0400 Subject: [PATCH] typespec and autorest.python working with vendored pygen --- packages/autorest.python/install.py | 49 ----- packages/autorest.python/package.json | 17 +- packages/autorest.python/run-python3.js | 2 +- .../autorest.python/scripts/copy-pygen.js | 15 ++ packages/autorest.python/scripts/install.py | 58 ++++++ .../autorest.python/{ => scripts}/start.py | 4 +- .../{ => scripts}/venvtools.py | 2 +- packages/pygen/dev_requirements.txt | 4 +- packages/pygen/run-python3.js | 193 ------------------ packages/pygen/{ => scripts}/install.py | 2 +- packages/pygen/{ => scripts}/prepare.py | 2 +- packages/pygen/scripts/run-python3.cjs | 22 ++ packages/pygen/{ => scripts}/run_tsp.py | 2 +- .../pygen/scripts/system-requirements.cjs | 180 ++++++++++++++++ packages/pygen/{ => scripts}/venvtools.py | 2 +- packages/typespec-python/package.json | 3 +- .../{post-build.js => post-install.js} | 2 +- packages/typespec-python/src/emitter.ts | 6 +- pnpm-lock.yaml | 27 ++- 19 files changed, 317 insertions(+), 275 deletions(-) delete mode 100644 packages/autorest.python/install.py create mode 100644 packages/autorest.python/scripts/copy-pygen.js create mode 100644 packages/autorest.python/scripts/install.py rename packages/autorest.python/{ => scripts}/start.py (90%) rename packages/autorest.python/{ => scripts}/venvtools.py (98%) delete mode 100644 packages/pygen/run-python3.js rename packages/pygen/{ => scripts}/install.py (97%) rename packages/pygen/{ => scripts}/prepare.py (96%) create mode 100644 packages/pygen/scripts/run-python3.cjs rename packages/pygen/{ => scripts}/run_tsp.py (97%) create mode 100644 packages/pygen/scripts/system-requirements.cjs rename packages/pygen/{ => scripts}/venvtools.py (98%) rename packages/typespec-python/scripts/{post-build.js => post-install.js} (89%) diff --git a/packages/autorest.python/install.py b/packages/autorest.python/install.py deleted file mode 100644 index fddd5e9fa6c..00000000000 --- a/packages/autorest.python/install.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python - -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -import sys - -if not sys.version_info >= (3, 8, 0): - raise Exception("Autorest for Python extension requires Python 3.8 at least") - -try: - import pip -except ImportError: - raise Exception("Your Python installation doesn't have pip available") - -try: - import venv -except ImportError: - raise Exception("Your Python installation doesn't have venv available") - - -# Now we have pip and Py >= 3.8, go to work - -from pathlib import Path - -from venvtools import ExtendedEnvBuilder, python_run - -_ROOT_DIR = Path(__file__).parent - - -def main(): - # we use pygen's venv - venv_path = _ROOT_DIR.parent / "pygen" / "venv" - if venv_path.exists(): - env_builder = venv.EnvBuilder(with_pip=True) - venv_context = env_builder.ensure_directories(venv_path) - else: - env_builder = ExtendedEnvBuilder(with_pip=True, upgrade_deps=True) - env_builder.create(venv_path) - venv_context = env_builder.context - - python_run(venv_context, "pip", ["install", "-r", "requirements.txt"]) - python_run(venv_context, "pip", ["install", "-e", str(_ROOT_DIR)]) - - -if __name__ == "__main__": - main() diff --git a/packages/autorest.python/package.json b/packages/autorest.python/package.json index 60265c00f44..c821ca61ea0 100644 --- a/packages/autorest.python/package.json +++ b/packages/autorest.python/package.json @@ -3,10 +3,10 @@ "version": "6.13.17", "description": "The Python extension for generators in AutoRest.", "scripts": { - "prepare": "node ./node_modules/@azure-tools/python-client-generator-core/run-python3.js ./node_modules/@azure-tools/python-client-generator-core/prepare.py", - "start": "node ./node_modules/@azure-tools/python-client-generator-core/run-python3.js start.py", - "install": "node ./node_modules/@azure-tools/python-client-generator-core/run-python3.js install.py", - "debug": "node ./node_modules/@azure-tools/python-client-generator-core/run-python3.js start.py --debug" + "prepare": "node ./node_modules/pygen/scripts/run-python3.cjs ./node_modules/pygen/scripts/prepare.py", + "start": "node ./node_modules/pygen/scripts/run-python3.cjs ./scripts/start.py", + "install": "node ./scripts/copy-pygen.js && node ./node_modules/pygen/scripts/run-python3.cjs ./node_modules/pygen/scripts/install.py && node ./node_modules/pygen/scripts/run-python3.cjs ./scripts/install.py", + "debug": "node ./node_modules/pygen/scripts/run-python3.cjs ./scripts/start.py --debug" }, "main": "index.js", "repository": { @@ -25,7 +25,9 @@ }, "homepage": "https://github.com/Azure/autorest.python/blob/main/README.md", "dependencies": { - "@autorest/system-requirements": "~1.0.2" + "@autorest/system-requirements": "~1.0.2", + "fs-extra": "^10.0.0", + "semver": "^7.3.5" }, "devDependencies": { "@microsoft.azure/autorest.testserver": "^3.3.46", @@ -34,12 +36,9 @@ "files": [ "autorest/**/*.py", "autorest/**/*.jinja2", + "scripts/", "setup.py", - "install.py", - "prepare.py", - "start.py", "venvtools.py", - "run-python3.js", "requirements.txt", "run_cadl.py" ] diff --git a/packages/autorest.python/run-python3.js b/packages/autorest.python/run-python3.js index 020cbce0a0f..70ec3bc1e6e 100644 --- a/packages/autorest.python/run-python3.js +++ b/packages/autorest.python/run-python3.js @@ -4,7 +4,7 @@ // path resolution algorithm as AutoRest so that the behavior // is fully consistent (and also supports AUTOREST_PYTHON_EXE). // -// Invoke it like so: "node run-python3.js script.py" +// Invoke it like so: "node run-python3.cjs script.py" const cp = require("child_process"); const extension = require("@autorest/system-requirements"); diff --git a/packages/autorest.python/scripts/copy-pygen.js b/packages/autorest.python/scripts/copy-pygen.js new file mode 100644 index 00000000000..800132d30c6 --- /dev/null +++ b/packages/autorest.python/scripts/copy-pygen.js @@ -0,0 +1,15 @@ +const fs = require('fs-extra'); +const path = require('path'); +const url = require('url'); + +// Define the source and destination directories +const sourceDir = path.join(__dirname, "..", "..", 'pygen'); +const destDir = path.join(__dirname, "..", "node_modules", "pygen"); + +// Define the filter function. Don't want to copy node_modules +const filterFunc = (src) => { + return src.indexOf('node_modules') === -1; + }; + +// Copy the source directory to the destination directory +fs.copySync(sourceDir, destDir, { filter: filterFunc }); diff --git a/packages/autorest.python/scripts/install.py b/packages/autorest.python/scripts/install.py new file mode 100644 index 00000000000..32a5e47f499 --- /dev/null +++ b/packages/autorest.python/scripts/install.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import sys + +if not sys.version_info >= (3, 8, 0): + raise Exception("Autorest for Python extension requires Python 3.8 at least") + +try: + import pip +except ImportError: + raise Exception("Your Python installation doesn't have pip available") + +try: + import venv +except ImportError: + raise Exception("Your Python installation doesn't have venv available") + + +# Now we have pip and Py >= 3.8, go to work + +from pathlib import Path +import shutil + +from venvtools import python_run + +_ROOT_DIR = Path(__file__).parent.parent + + +def ignore_node_modules(dirname, filenames): + return ["node_modules"] if "node_modules" in filenames else [] + + +def main(): + # Define the source and destination directories + source_dir = _ROOT_DIR.parent / "pygen" + dest_dir = _ROOT_DIR / "node_modules" / "pygen" + + # Copy the source directory to the destination directory + shutil.copytree(source_dir, dest_dir, dirs_exist_ok=True, ignore=ignore_node_modules) + + # we use pygen's venv + venv_path = dest_dir / "venv" + assert venv_path.exists() # Otherwise install was not done + + env_builder = venv.EnvBuilder(with_pip=True) + venv_context = env_builder.ensure_directories(venv_path) + # install autorest.python specific stuff into it + python_run(venv_context, "pip", ["install", "-r", "requirements.txt"]) + python_run(venv_context, "pip", ["install", "-e", str(_ROOT_DIR)]) + + +if __name__ == "__main__": + main() diff --git a/packages/autorest.python/start.py b/packages/autorest.python/scripts/start.py similarity index 90% rename from packages/autorest.python/start.py rename to packages/autorest.python/scripts/start.py index 85053e82ff7..38fb337b59b 100644 --- a/packages/autorest.python/start.py +++ b/packages/autorest.python/scripts/start.py @@ -15,12 +15,12 @@ from venvtools import python_run -_ROOT_DIR = Path(__file__).parent +_ROOT_DIR = Path(__file__).parent.parent def main(): # we use pygen's venv - venv_path = _ROOT_DIR.parent / "pygen" / "venv" + venv_path = _ROOT_DIR / "node_modules" / "pygen" / "venv" venv_prexists = venv_path.exists() assert venv_prexists # Otherwise install was not done diff --git a/packages/autorest.python/venvtools.py b/packages/autorest.python/scripts/venvtools.py similarity index 98% rename from packages/autorest.python/venvtools.py rename to packages/autorest.python/scripts/venvtools.py index 01e1300b1c8..944ff96e36b 100644 --- a/packages/autorest.python/venvtools.py +++ b/packages/autorest.python/scripts/venvtools.py @@ -11,7 +11,7 @@ from pathlib import Path -_ROOT_DIR = Path(__file__).parent +_ROOT_DIR = Path(__file__).parent.parent class ExtendedEnvBuilder(venv.EnvBuilder): diff --git a/packages/pygen/dev_requirements.txt b/packages/pygen/dev_requirements.txt index d458756b7d8..652362d3aa5 100644 --- a/packages/pygen/dev_requirements.txt +++ b/packages/pygen/dev_requirements.txt @@ -1,5 +1,5 @@ -e . --r ../../eng/requirements.txt --r ../../eng/dev_requirements.txt +-r ../../../../eng/requirements.txt +-r ../../../../eng/dev_requirements.txt ptvsd==4.3.2 types-PyYAML==6.0.12.8 diff --git a/packages/pygen/run-python3.js b/packages/pygen/run-python3.js deleted file mode 100644 index 7a309ff8052..00000000000 --- a/packages/pygen/run-python3.js +++ /dev/null @@ -1,193 +0,0 @@ -// This script wraps logic in @azure-tools/extension to resolve -// the path to Python 3 so that a Python script file can be run -// from an npm script in package.json. It uses the same Python 3 -// path resolution algorithm as AutoRest so that the behavior -// is fully consistent (and also supports AUTOREST_PYTHON_EXE). -// -// Invoke it like so: "node run-python3.js script.py" - -const cp = require("child_process"); - -async function runPython3(scriptName, ...args) { - const command = await patchPythonPath(["python", scriptName, ...args], { version: ">=3.8", environmentVariable: "AUTOREST_PYTHON_EXE" }); - cp.execSync(command.join(" "), { - stdio: [0, 1, 2] - }); -} - -runPython3(...process.argv.slice(2)).catch(err => { - console.error(err.toString()); - process.exit(1); -}); - -/* - * Copied from @autorest/system-requirements - */ - -const PythonRequirement = "python"; -const PRINT_PYTHON_VERSION_SCRIPT = "import sys; print('.'.join(map(str, sys.version_info[:3])))"; -const semver_1 = require("semver"); - -const execute = (command, cmdlineargs, options = {}) => { - return new Promise((resolve, reject) => { - const cp = (0, child_process_1.spawn)(command, cmdlineargs, { ...options, stdio: "pipe", shell: true }); - if (options.onCreate) { - options.onCreate(cp); - } - options.onStdOutData ? cp.stdout.on("data", options.onStdOutData) : cp; - options.onStdErrData ? cp.stderr.on("data", options.onStdErrData) : cp; - let err = ""; - let out = ""; - let all = ""; - cp.stderr.on("data", (chunk) => { - err += chunk; - all += chunk; - }); - cp.stdout.on("data", (chunk) => { - out += chunk; - all += chunk; - }); - cp.on("error", (err) => { - reject(err); - }); - cp.on("close", (code, signal) => resolve({ - stdout: out, - stderr: err, - log: all, - error: code ? new Error("Process Failed.") : null, - code, - })); - }); -}; - -const versionIsSatisfied = (version, requirement) => { - const cleanedVersion = semver_1.default.coerce(version); - if (!cleanedVersion) { - throw new Error(`Invalid version ${version}.`); - } - return semver_1.default.satisfies(cleanedVersion, requirement, true); -}; - -/** - * Validate the provided system requirement resolution is satisfying the version requirement if applicable. - * @param resolution Command resolution. - * @param actualVersion Version for that resolution. - * @param requirement Requirement. - * @returns the resolution if it is valid or an @see SystemRequirementError if not. - */ -const validateVersionRequirement = (resolution, actualVersion, requirement) => { - if (!requirement.version) { - return resolution; // No version requirement. - } - try { - if ((0, versionIsSatisfied)(actualVersion, requirement.version)) { - return resolution; - } - return { - ...resolution, - error: true, - message: `'${resolution.command}' version is '${actualVersion}' but doesn't satisfy requirement '${requirement.version}'. Please update.`, - actualVersion: actualVersion, - neededVersion: requirement.version, - }; - } - catch (_a) { - return { - ...resolution, - error: true, - message: `Couldn't parse the version ${actualVersion}. This is not a valid semver version.`, - actualVersion: actualVersion, - neededVersion: requirement.version, - }; - } -}; - -const tryPython = async (requirement, command, additionalArgs = []) => { - const resolution = { - name: PythonRequirement, - command, - additionalArgs: additionalArgs.length > 0 ? additionalArgs : undefined, - }; - try { - const result = await (0, execute)(command, [...additionalArgs, "-c", `"${PRINT_PYTHON_VERSION_SCRIPT}"`]); - return (0, validateVersionRequirement)(resolution, result.stdout.trim(), requirement); - } - catch (e) { - return { - error: true, - ...resolution, - message: `'${command}' command line is not found in the path. Make sure to have it installed.`, - }; - } -}; - -/** - * Returns the path to the executable as asked in the requirement. - * @param requirement System requirement definition. - * @returns If the requirement provide an environment variable for the path returns the value of that environment variable. undefined otherwise. - */ -const getExecutablePath = (requirement) => requirement.environmentVariable && process.env[requirement.environmentVariable]; - -const createPythonErrorMessage = (requirement, errors) => { - var _a; - const versionReq = (_a = requirement.version) !== null && _a !== void 0 ? _a : "*"; - const lines = [ - `Couldn't find a valid python interpreter satisfying the requirement (version: ${versionReq}). Tried:`, - ...errors.map((x) => ` - ${x.command} (${x.message})`), - ]; - return { - error: true, - name: "python", - command: "python", - message: lines.join("\n"), - }; -}; - -const resolvePythonRequirement = async (requirement) => { - var _a; - // Hardcoding AUTOREST_PYTHON_EXE is for backward compatibility - const path = (_a = (0, getExecutablePath)(requirement)) !== null && _a !== void 0 ? _a : process.env["AUTOREST_PYTHON_EXE"]; - if (path) { - return await tryPython(requirement, path); - } - const errors = []; - // On windows try `py` executable with `-3` flag. - if (process.platform === "win32") { - const pyResult = await tryPython(requirement, "py", ["-3"]); - if ("error" in pyResult) { - errors.push(pyResult); - } - else { - return pyResult; - } - } - const python3Result = await tryPython(requirement, "python3"); - if ("error" in python3Result) { - errors.push(python3Result); - } - else { - return python3Result; - } - const pythonResult = await tryPython(requirement, "python"); - if ("error" in pythonResult) { - errors.push(pythonResult); - } - else { - return pythonResult; - } - return createPythonErrorMessage(requirement, errors); -}; - -/** - * @param command list of the command and arguments. First item in array must be a python exe @see KnownPythonExe. (e.g. ["python", "mypythonfile.py"] - * @param requirement - */ -const patchPythonPath = async (command, requirement) => { - var _a; - const [_, ...args] = command; - const resolution = await (0, resolvePythonRequirement)(requirement); - if ("error" in resolution) { - throw new Error(`Failed to find compatible python version. ${resolution.message}`); - } - return [resolution.command, ...((_a = resolution.additionalArgs) !== null && _a !== void 0 ? _a : []), ...args]; -}; diff --git a/packages/pygen/install.py b/packages/pygen/scripts/install.py similarity index 97% rename from packages/pygen/install.py rename to packages/pygen/scripts/install.py index 237a982b0c6..ec223c860a5 100644 --- a/packages/pygen/install.py +++ b/packages/pygen/scripts/install.py @@ -27,7 +27,7 @@ from venvtools import ExtendedEnvBuilder, python_run -_ROOT_DIR = Path(__file__).parent +_ROOT_DIR = Path(__file__).parent.parent def main(): diff --git a/packages/pygen/prepare.py b/packages/pygen/scripts/prepare.py similarity index 96% rename from packages/pygen/prepare.py rename to packages/pygen/scripts/prepare.py index b7e9b2dc781..2121ac8b3a0 100644 --- a/packages/pygen/prepare.py +++ b/packages/pygen/scripts/prepare.py @@ -16,7 +16,7 @@ from venvtools import python_run -_ROOT_DIR = Path(__file__).parent +_ROOT_DIR = Path(__file__).parent.parent def main(): diff --git a/packages/pygen/scripts/run-python3.cjs b/packages/pygen/scripts/run-python3.cjs new file mode 100644 index 00000000000..6059b4cce09 --- /dev/null +++ b/packages/pygen/scripts/run-python3.cjs @@ -0,0 +1,22 @@ +// This script wraps logic in @azure-tools/extension to resolve +// the path to Python 3 so that a Python script file can be run +// from an npm script in package.json. It uses the same Python 3 +// path resolution algorithm as AutoRest so that the behavior +// is fully consistent (and also supports AUTOREST_PYTHON_EXE). +// +// Invoke it like so: "node run-python3.cjs script.py" + +const cp = require("child_process"); +const extension = require("./system-requirements.cjs"); + +async function runPython3(scriptName, ...args) { + const command = await extension.patchPythonPath(["python", scriptName, ...args], { version: ">=3.8", environmentVariable: "AUTOREST_PYTHON_EXE" }); + cp.execSync(command.join(" "), { + stdio: [0, 1, 2] + }); + } + + runPython3(...process.argv.slice(2)).catch(err => { + console.error(err.toString()); + process.exit(1); + }); diff --git a/packages/pygen/run_tsp.py b/packages/pygen/scripts/run_tsp.py similarity index 97% rename from packages/pygen/run_tsp.py rename to packages/pygen/scripts/run_tsp.py index d9bb0d20910..311e470ba58 100644 --- a/packages/pygen/run_tsp.py +++ b/packages/pygen/scripts/run_tsp.py @@ -9,7 +9,7 @@ from pathlib import Path from venvtools import python_run -_ROOT_DIR = Path(__file__).parent +_ROOT_DIR = Path(__file__).parent.parent _LOGGER = logging.getLogger(__name__) diff --git a/packages/pygen/scripts/system-requirements.cjs b/packages/pygen/scripts/system-requirements.cjs new file mode 100644 index 00000000000..2b7ced2c400 --- /dev/null +++ b/packages/pygen/scripts/system-requirements.cjs @@ -0,0 +1,180 @@ +const semver = require("semver"); +const child_process = require("child_process"); + +/* + * Copied from @autorest/system-requirements + */ + +const PythonRequirement = "python"; +const PRINT_PYTHON_VERSION_SCRIPT = "import sys; print('.'.join(map(str, sys.version_info[:3])))"; + + +function execute(command, cmdlineargs, options = {}) { + return new Promise((resolve, reject) => { + const cp = child_process.spawn(command, cmdlineargs, { ...options, stdio: "pipe", shell: true }); + if (options.onCreate) { + options.onCreate(cp); + } + + options.onStdOutData ? cp.stdout.on("data", options.onStdOutData) : cp; + options.onStdErrData ? cp.stderr.on("data", options.onStdErrData) : cp; + + let err = ""; + let out = ""; + let all = ""; + cp.stderr.on("data", (chunk) => { + err += chunk; + all += chunk; + }); + cp.stdout.on("data", (chunk) => { + out += chunk; + all += chunk; + }); + + cp.on("error", (err) => { + reject(err); + }); + cp.on("close", (code, signal) => + resolve({ + stdout: out, + stderr: err, + log: all, + error: code ? new Error("Process Failed.") : null, + code, + }), + ); + }); +}; + +function versionIsSatisfied(version, requirement) { + const cleanedVersion = semver.coerce(version); + if (!cleanedVersion) { + throw new Error(`Invalid version ${version}.`); + } + return semver.satisfies(cleanedVersion, requirement, true); +}; + +/** + * Validate the provided system requirement resolution is satisfying the version requirement if applicable. + * @param resolution Command resolution. + * @param actualVersion Version for that resolution. + * @param requirement Requirement. + * @returns the resolution if it is valid or an @see SystemRequirementError if not. + */ +function validateVersionRequirement(resolution, actualVersion, requirement) { + if (!requirement.version) { + return resolution; // No version requirement. + } + + try { + if (versionIsSatisfied(actualVersion, requirement.version)) { + return resolution; + } + return { + ...resolution, + error: true, + message: `'${resolution.command}' version is '${actualVersion}' but doesn't satisfy requirement '${requirement.version}'. Please update.`, + actualVersion: actualVersion, + neededVersion: requirement.version, + }; + } catch { + return { + ...resolution, + error: true, + message: `Couldn't parse the version ${actualVersion}. This is not a valid semver version.`, + actualVersion: actualVersion, + neededVersion: requirement.version, + }; + } +}; + +async function tryPython(requirement, command, additionalArgs = []) { + const resolution = { + name: PythonRequirement, + command, + additionalArgs: additionalArgs.length > 0 ? additionalArgs : undefined, + }; + + try { + const result = await execute(command, [...additionalArgs, "-c", `"${PRINT_PYTHON_VERSION_SCRIPT}"`]); + return validateVersionRequirement(resolution, result.stdout.trim(), requirement); + } catch (e) { + return { + error: true, + ...resolution, + message: `'${command}' command line is not found in the path. Make sure to have it installed.`, + }; + } +}; + +/** + * Returns the path to the executable as asked in the requirement. + * @param requirement System requirement definition. + * @returns If the requirement provide an environment variable for the path returns the value of that environment variable. undefined otherwise. + */ +function getExecutablePath(requirement) { + return requirement.environmentVariable && process.env[requirement.environmentVariable]; +} + +function createPythonErrorMessage(requirement, errors) { + const versionReq = requirement.version ?? "*"; + const lines = [ + `Couldn't find a valid python interpreter satisfying the requirement (version: ${versionReq}). Tried:`, + ...errors.map((x) => ` - ${x.command} (${x.message})`), + ]; + + return { + error: true, + name: "python", + command: "python", + message: lines.join("\n"), + }; +}; + +async function resolvePythonRequirement(requirement) { + const path = getExecutablePath(requirement) ?? process.env["AUTOREST_PYTHON_EXE"]; + if (path) { + return await tryPython(requirement, path); + } + + const errors = []; + // On windows try `py` executable with `-3` flag. + if (process.platform === "win32") { + const pyResult = await tryPython(requirement, "py", ["-3"]); + if ("error" in pyResult) { + errors.push(pyResult); + } else { + return pyResult; + } + } + + const python3Result = await tryPython(requirement, "python3"); + if ("error" in python3Result) { + errors.push(python3Result); + } else { + return python3Result; + } + + const pythonResult = await tryPython(requirement, "python"); + if ("error" in pythonResult) { + errors.push(pythonResult); + } else { + return pythonResult; + } + + return createPythonErrorMessage(requirement, errors); +}; + +/** + * @param command list of the command and arguments. First item in array must be a python exe @see KnownPythonExe. (e.g. ["python", "mypythonfile.py"] + * @param requirement + */ +async function patchPythonPath(command, requirement) { + const [_, ...args] = command; + const resolution = await resolvePythonRequirement(requirement); + if ("error" in resolution) { + throw new Error(`Failed to find compatible python version. ${resolution.message}`); + } + return [resolution.command, ...(resolution.additionalArgs ?? []), ...args]; +}; +module.exports.patchPythonPath = patchPythonPath; diff --git a/packages/pygen/venvtools.py b/packages/pygen/scripts/venvtools.py similarity index 98% rename from packages/pygen/venvtools.py rename to packages/pygen/scripts/venvtools.py index 01e1300b1c8..944ff96e36b 100644 --- a/packages/pygen/venvtools.py +++ b/packages/pygen/scripts/venvtools.py @@ -11,7 +11,7 @@ from pathlib import Path -_ROOT_DIR = Path(__file__).parent +_ROOT_DIR = Path(__file__).parent.parent class ExtendedEnvBuilder(venv.EnvBuilder): diff --git a/packages/typespec-python/package.json b/packages/typespec-python/package.json index 007fac31f70..4a93aff75bd 100644 --- a/packages/typespec-python/package.json +++ b/packages/typespec-python/package.json @@ -27,8 +27,9 @@ "node": ">=14.0.0" }, "scripts": { + "postinstall": "node ./scripts/post-install.js && node ./dist/pygen/scripts/run-python3.cjs ./dist/pygen/scripts/install.py && node ./dist/pygen/scripts/run-python3.cjs ./dist/pygen/scripts/prepare.py", "clean": "rimraf ./dist ./temp", - "build": "tsc -p . && node ./scripts/post-build.js", + "build": "tsc -p .", "watch": "tsc -p . --watch", "test": "mocha", "test-official": "c8 mocha --forbid-only", diff --git a/packages/typespec-python/scripts/post-build.js b/packages/typespec-python/scripts/post-install.js similarity index 89% rename from packages/typespec-python/scripts/post-build.js rename to packages/typespec-python/scripts/post-install.js index e6483f0d742..c4f67cc7249 100644 --- a/packages/typespec-python/scripts/post-build.js +++ b/packages/typespec-python/scripts/post-install.js @@ -6,7 +6,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)) // Define the source and destination directories const sourceDir = join(__dirname, "..", "..", 'pygen'); -const destDir = join(__dirname, "..", 'dist', "src", "pygen"); +const destDir = join(__dirname, "..", 'dist', "pygen"); // Define the filter function. Don't want to copy node_modules const filterFunc = (src) => { diff --git a/packages/typespec-python/src/emitter.ts b/packages/typespec-python/src/emitter.ts index 63e35f41bbd..785c30a8f1b 100644 --- a/packages/typespec-python/src/emitter.ts +++ b/packages/typespec-python/src/emitter.ts @@ -67,14 +67,14 @@ function createPythonSdkContext( export async function $onEmit(context: EmitContext) { const program = context.program; const sdkContext = createPythonSdkContext(context); - const root = path.join(dirname(fileURLToPath(import.meta.url)), "pygen"); + const root = path.join(dirname(fileURLToPath(import.meta.url)), "..", "pygen"); const outputDir = context.emitterOutputDir; const yamlMap = emitCodeModel(sdkContext); addDefaultOptions(sdkContext); const yamlPath = await saveCodeModelAsYaml("typespec-python-yaml-map", yamlMap); const commandArgs = [ - `${root}/run-python3.js`, - `${root}/run_tsp.py`, + `${root}/scripts/run-python3.cjs`, + `${root}/scripts/run_tsp.py`, `--output-folder=${outputDir}`, `--cadl-file=${yamlPath}`, ]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0fdbf7113e..10174aa8a8b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,12 @@ importers: '@autorest/system-requirements': specifier: ~1.0.2 version: 1.0.2 + fs-extra: + specifier: ^10.0.0 + version: 10.1.0 + semver: + specifier: ^7.3.5 + version: 7.6.2 devDependencies: '@microsoft.azure/autorest.testserver': specifier: ^3.3.46 @@ -166,7 +172,7 @@ packages: dependencies: '@azure/logger': 1.0.4 command-exists: 1.2.9 - semver: 7.5.4 + semver: 7.6.2 dev: false /@azure-tools/cadl-ranch-api@0.4.3: @@ -3475,6 +3481,15 @@ packages: engines: {node: '>= 0.6'} dev: true + /fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + dev: false + /fs-extra@11.1.1: resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} engines: {node: '>=14.14'} @@ -4511,6 +4526,7 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 + dev: true /make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -5685,14 +5701,6 @@ packages: lru-cache: 6.0.0 dev: true - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: false - /semver@7.6.0: resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} engines: {node: '>=10'} @@ -6688,6 +6696,7 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true /yaml@2.4.1: resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==}