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
This commit is contained in:
Laurens Valk
2026-03-19 09:11:02 +01:00
parent a18d88dffd
commit f3dc7b55b7
3 changed files with 46 additions and 6 deletions
+21
View File
@@ -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();
});
});
+13
View File
@@ -381,6 +381,18 @@ const useLegacyStartUserProgram: Reducer<boolean> = (state = false, action) => {
return state;
};
/**
* When true, use the legacy `__main__` module name instead of the actual file name.
*/
const useLegacyMainModule: Reducer<boolean> = (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,
});
+12 -6
View File
@@ -104,8 +104,8 @@ function* handleCompile(action: ReturnType<typeof compile>): 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<string, FileContents>([
[mainPyName, { path: mainPyPath, contents: mainPy }],
[mainPyName, { path: mainPyPath, contents: mainPyContents }],
]);
const checkedModules = new Set<string>([mainPyName]);
const uncheckedScripts = new Array<string>(mainPy);
const uncheckedScripts = new Array<string>(mainPyContents);
for (;;) {
// parse all unchecked scripts to find imported modules that haven't