From f3dc7b55b78a731346e10902397fb3434c2eca22 Mon Sep 17 00:00:00 2001 From: Laurens Valk Date: Thu, 19 Mar 2026 09:11:02 +0100 Subject: [PATCH] sagas/mpy: Use legacy `__main__` on old firmware. Needed for backwards compatibility. Older firmware always look for __main__. See https://github.com/pybricks/support/issues/2364 --- src/hub/reducers.test.ts | 21 +++++++++++++++++++++ src/hub/reducers.ts | 13 +++++++++++++ src/mpy/sagas.ts | 18 ++++++++++++------ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/hub/reducers.test.ts b/src/hub/reducers.test.ts index 87b081a2..b65c2d3d 100644 --- a/src/hub/reducers.test.ts +++ b/src/hub/reducers.test.ts @@ -58,6 +58,7 @@ test('initial state', () => { "runtime": "hub.runtime.disconnected", "selectedSlot": 0, "useLegacyDownload": false, + "useLegacyMainModule": false, "useLegacyStartUserProgram": false, "useLegacyStdio": false, } @@ -544,3 +545,23 @@ describe('useLegacyStartUserProgram', () => { ).toBeFalsy(); }); }); + +describe('useLegacyMainModule', () => { + test('Pybricks Profile < v1.5.0', () => { + expect( + reducers( + { useLegacyMainModule: false } as State, + bleDIServiceDidReceiveSoftwareRevision('1.4.0'), + ).useLegacyMainModule, + ).toBeTruthy(); + }); + + test('Pybricks Profile >= v1.5.0', () => { + expect( + reducers( + { useLegacyMainModule: true } as State, + bleDIServiceDidReceiveSoftwareRevision('1.5.0'), + ).useLegacyMainModule, + ).toBeFalsy(); + }); +}); diff --git a/src/hub/reducers.ts b/src/hub/reducers.ts index c4809250..242c2958 100644 --- a/src/hub/reducers.ts +++ b/src/hub/reducers.ts @@ -381,6 +381,18 @@ const useLegacyStartUserProgram: Reducer = (state = false, action) => { return state; }; +/** + * When true, use the legacy `__main__` module name instead of the actual file name. + */ +const useLegacyMainModule: Reducer = (state = false, action) => { + if (bleDIServiceDidReceiveSoftwareRevision.matches(action)) { + // Behavior changed starting with Pybricks Profile v1.5.0. + return !semver.satisfies(action.version, '^1.5.0'); + } + + return state; +}; + /* * Returns number of available slots or 0 for slots not supported. */ @@ -418,6 +430,7 @@ export default combineReducers({ useLegacyDownload, useLegacyStdio, useLegacyStartUserProgram, + useLegacyMainModule, numOfSlots, selectedSlot, }); diff --git a/src/mpy/sagas.ts b/src/mpy/sagas.ts index fe574b5f..b8c284a1 100644 --- a/src/mpy/sagas.ts +++ b/src/mpy/sagas.ts @@ -104,8 +104,8 @@ function* handleCompile(action: ReturnType): Generator { /** * Compiles code into the Pybricks multi-mpy6 file format. * - * This includes a __main__ module which is the file currently open in the - * editor and any imported modules that can be found in the user file system. + * This includes the file currently open in the editor and any imported modules + * that can be found in the user file system. */ function* handleCompileMulti6(): Generator { // REVISIT: should we be getting the active file here or have it as an @@ -128,16 +128,22 @@ function* handleCompileMulti6(): Generator { return; } - const mainPy = yield* editorGetValue(); + const useLegacyMainModule = yield* select( + (s: RootState) => s.hub.useLegacyMainModule, + ); + + const mainPyContents = yield* editorGetValue(); const mainPyPath = metadata.path ?? '__main__.py'; - const mainPyName = mainPyPath.replace(/\.[^.]+$/, ''); + const mainPyName = useLegacyMainModule + ? '__main__' + : mainPyPath.replace(/\.[^.]+$/, ''); const pyFiles = new Map([ - [mainPyName, { path: mainPyPath, contents: mainPy }], + [mainPyName, { path: mainPyPath, contents: mainPyContents }], ]); const checkedModules = new Set([mainPyName]); - const uncheckedScripts = new Array(mainPy); + const uncheckedScripts = new Array(mainPyContents); for (;;) { // parse all unchecked scripts to find imported modules that haven't