diff --git a/localtypings/pxtarget.d.ts b/localtypings/pxtarget.d.ts index 6e91644dbf4d..ee4495e1a508 100644 --- a/localtypings/pxtarget.d.ts +++ b/localtypings/pxtarget.d.ts @@ -738,6 +738,7 @@ declare namespace ts.pxtc { flashChecksumAddr?: number; ramSize?: number; patches?: pxt.Map; // semver range -> upgrade policies + pyPatches?: pxt.Map; // semver range -> upgrade policies openocdScript?: string; uf2Family?: string; onStartText?: boolean; diff --git a/pxtcompiler/simpledriver.ts b/pxtcompiler/simpledriver.ts index 8b7aef04fd70..c7a3809d274f 100644 --- a/pxtcompiler/simpledriver.ts +++ b/pxtcompiler/simpledriver.ts @@ -172,6 +172,9 @@ namespace pxt { return mainPkg.getCompileOptionsAsync(target) }).then(opts => { patchTS(mainPkg.targetVersion(), opts) + if (mainPkg.getPreferredEditor() === pxt.PYTHON_PROJECT_NAME) { + patchPY(mainPkg.targetVersion(), opts) + } prepPythonOptions(opts) return opts }) @@ -209,6 +212,22 @@ namespace pxt { } } + export function patchPY(version: string, opts: pxtc.CompileOptions) { + if (!version) + return + pxt.debug(`applying PY patches relative to ${version}`) + for (let fn of Object.keys(opts.fileSystem)) { + if (fn.indexOf("/") == -1 && U.endsWith(fn, ".py")) { + const initial = opts.fileSystem[fn] + const patched = pxt.patching.patchPython(version, initial) + if (initial != patched) { + pxt.debug(`applying PY patch to ${fn}`) + opts.fileSystem[fn] = patched + } + } + } + } + // eslint-disable-next-line no-var declare var global: any; // eslint-disable-next-line no-var diff --git a/pxtlib/patch.ts b/pxtlib/patch.ts index 1fb6b426bfa6..99b22b7b3b3d 100644 --- a/pxtlib/patch.ts +++ b/pxtlib/patch.ts @@ -2,6 +2,16 @@ namespace pxt.patching { export function computePatches(version: string, kind?: string): ts.pxtc.UpgradePolicy[] { const patches = pxt.appTarget.compile ? pxt.appTarget.compile.patches : undefined; if (!patches) return undefined; + return parsePatches(version, patches, kind); + } + + export function computePyPatches(version: string, kind?: string): ts.pxtc.UpgradePolicy[] { + const patches = pxt.appTarget.compile ? pxt.appTarget.compile.pyPatches : undefined; + if (!patches) return undefined; + return parsePatches(version, patches, kind); + } + + function parsePatches(version: string, patches: Map, kind?: string): ts.pxtc.UpgradePolicy[] { const v = pxt.semver.tryParse(version || "0.0.0") || pxt.semver.tryParse("0.0.0"); let r: ts.pxtc.UpgradePolicy[] = []; Object.keys(patches) @@ -30,6 +40,15 @@ namespace pxt.patching { export function patchJavaScript(pkgTargetVersion: string, fileContents: string): string { const upgrades = pxt.patching.computePatches(pkgTargetVersion); + return patchTextCode(pkgTargetVersion, fileContents, upgrades); + } + + export function patchPython(pkgTargetVersion: string, fileContents: string): string { + const upgrades = pxt.patching.computePyPatches(pkgTargetVersion); + return patchTextCode(pkgTargetVersion, fileContents, upgrades); + } + + function patchTextCode(pkgTargetVersion: string, fileContents: string, upgrades: pxtc.UpgradePolicy[]): string { let updatedContents = fileContents; if (upgrades) { upgrades.filter(u => u.type === "api").forEach(rule => { diff --git a/webapp/src/compiler.ts b/webapp/src/compiler.ts index 17c96264fac0..ac06e0b0de62 100644 --- a/webapp/src/compiler.ts +++ b/webapp/src/compiler.ts @@ -835,7 +835,12 @@ export function applyUpgradesAsync(): Promise { }); } - const upgradeOp = epkg.header.editor !== pxt.BLOCKS_PROJECT_NAME ? upgradeFromTSAsync : upgradeFromBlocksAsync; + const upgradeOp = + epkg.header.editor !== pxt.BLOCKS_PROJECT_NAME + ? epkg.header.editor !== pxt.PYTHON_PROJECT_NAME + ? upgradeFromTSAsync + : upgradeFromPythonAsync + : upgradeFromBlocksAsync; let projectNeverCompiled = false; @@ -922,7 +927,7 @@ function upgradeFromBlocksAsync(): Promise { }); } -function upgradeFromTSAsync(): Promise { +async function upgradeFromTSAsync(): Promise { const mainPkg = pkg.mainPkg; const project = pkg.getEditorPkg(mainPkg); const targetVersion = project.header.targetVersion; @@ -937,20 +942,49 @@ function upgradeFromTSAsync(): Promise { pxt.debug("Applying upgrades to TypeScript") - return checkPatchAsync(patchedFiles) - .then(() => { - return { - success: true, - editor: pxt.JAVASCRIPT_PROJECT_NAME, - patchedFiles - }; - }) - .catch(e => { - return { - success: false, - errorCodes: e.errorCodes - }; - }); + try { + await checkPatchAsync(patchedFiles) + return { + success: true, + editor: pxt.JAVASCRIPT_PROJECT_NAME, + patchedFiles + }; + } catch (e) { + return { + success: false, + errorCodes: e.errorCodes + }; + }; +} + +async function upgradeFromPythonAsync(): Promise { + const mainPkg = pkg.mainPkg; + const project = pkg.getEditorPkg(mainPkg); + const targetVersion = project.header.targetVersion; + + const patchedFiles: pxt.Map = {}; + pxt.Util.values(project.files).filter(isPyFile).forEach(file => { + const patched = pxt.patching.patchPython(targetVersion, file.content); + if (patched != file.content) { + patchedFiles[file.name] = patched; + } + }); + + pxt.debug("Applying upgrades to Python") + + try { + await checkPatchAsync(patchedFiles); + return { + success: true, + editor: pxt.PYTHON_PROJECT_NAME, + patchedFiles + }; + } catch (e) { + return { + success: false, + errorCodes: e.errorCodes + }; + } } interface UpgradeError extends Error { @@ -1000,6 +1034,10 @@ function isTsFile(file: pkg.File) { return pxt.Util.endsWith(file.getName(), ".ts"); } +function isPyFile(file: pkg.File) { + return pxt.Util.endsWith(file.getName(), ".py"); +} + export function updatePackagesAsync(packages: pkg.EditorPackage[], token?: pxt.Util.CancellationToken): Promise { const epkg = pkg.mainEditorPkg(); let backup: pxt.workspace.Header;