Skip to content

Commit d8493f0

Browse files
AppManager: try .mpy after .py and use import instead of explicit compile
1 parent 673b97e commit d8493f0

4 files changed

Lines changed: 63 additions & 97 deletions

File tree

internal_filesystem/lib/mpos/content/app_manager.py

Lines changed: 49 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -312,95 +312,70 @@ def is_installed_by_name(app_fullname):
312312
return AppManager.is_installed_by_path(f"apps/{app_fullname}") or AppManager.is_installed_by_path(f"builtin/apps/{app_fullname}")
313313

314314
@staticmethod
315-
def execute_script(script_source, is_file, classname, cwd=None, app_fullname=None):
316-
"""Run the script in the current thread. Returns True if successful."""
315+
def execute_script(script_source, classname, cwd=None, app_fullname=None):
316+
"""Run an app entrypoint file by importing its module. Returns True if successful."""
317317
import utime # for timing read and compile
318318
import mpos.ui
319319
import _thread
320320
import sys
321+
322+
def _start_activity(main_activity, source_name):
323+
if main_activity:
324+
from mpos.activity_navigator import ActivityNavigator
325+
from .intent import Intent
326+
327+
start_time = utime.ticks_ms()
328+
ActivityNavigator.startActivity(
329+
Intent(activity_class=main_activity, app_fullname=app_fullname)
330+
)
331+
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
332+
print(
333+
f"execute_script: ActivityNavigator.startActivity took {end_time}ms ({source_name})"
334+
)
335+
return True
336+
print(f"Warning: could not find app's main_activity {classname}")
337+
return False
338+
321339
thread_id = _thread.get_ident()
322-
compile_name = 'script' if not is_file else script_source
340+
compile_name = script_source
323341
executed_name = compile_name
324342
print(f"Thread {thread_id}: executing script with cwd: {cwd}")
325343
try:
326-
script_globals = {
327-
'__name__': "__main__", # in case the script wants this
328-
'__file__': compile_name # useful for logger
329-
}
330344
print(f"Thread {thread_id}: starting script")
331345
path_before = sys.path[:] # Make a copy, not a reference
332346
if cwd:
333-
sys.path.append(cwd)
347+
if cwd in sys.path:
348+
sys.path.remove(cwd)
349+
sys.path.insert(0, cwd)
334350
try:
335-
if is_file and script_source.endswith(".py"):
336-
mpy_source = script_source[:-3] + ".mpy"
337-
try:
338-
if os.stat(mpy_source)[0] & 0x8000:
339-
print(f"Thread {thread_id}: found precompiled script {mpy_source}")
340-
executed_name = mpy_source
341-
module_name = mpy_source.rsplit("/", 1)[-1][:-4]
342-
import builtins
343-
if module_name in sys.modules:
344-
del sys.modules[module_name]
345-
try:
346-
print(f"app_manager.py trying to __import__({module_name})")
347-
module = __import__(module_name)
348-
except Exception as e:
349-
print(f"app_manager.py failed to __import__({module_name}")
350-
module.__file__ = mpy_source
351-
module.__name__ = "__main__"
352-
main_activity = getattr(module, classname, None)
353-
if main_activity:
354-
from mpos.activity_navigator import ActivityNavigator
355-
from .intent import Intent
356-
start_time = utime.ticks_ms()
357-
ActivityNavigator.startActivity(Intent(activity_class=main_activity, app_fullname=app_fullname))
358-
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
359-
print(f"execute_script: ActivityNavigator.startActivity took {end_time}ms")
360-
return True
361-
raise Exception("could not find app's main_activity {} in {}".format(classname, mpy_source))
362-
except OSError:
363-
pass
364-
except Exception as e:
365-
print(f"WARNING: failed running precompiled script {mpy_source}: {e}")
366-
sys.print_exception(e)
367-
368-
if is_file:
369-
print(f"Thread {thread_id}: reading script from file {script_source}")
370-
with open(script_source, 'r') as f: # No need to check if it exists as exceptions are caught
371-
start_time = utime.ticks_ms()
372-
script_source = f.read()
373-
read_time = utime.ticks_diff(utime.ticks_ms(), start_time)
374-
print(f"execute_script: reading script_source took {read_time}ms")
375-
376-
start_time = utime.ticks_ms()
377-
compiled_script = compile(script_source, compile_name, 'exec')
378-
compile_time = utime.ticks_diff(utime.ticks_ms(), start_time)
379-
print(f"execute_script: compiling script_source took {compile_time}ms")
380-
start_time = utime.ticks_ms()
381-
exec(compiled_script, script_globals)
382-
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
383-
print(f"apps.py execute_script: exec took {end_time}ms")
384-
# Introspect globals
385-
classes = {k: v for k, v in script_globals.items() if isinstance(v, type)}
386-
functions = {k: v for k, v in script_globals.items() if callable(v) and not isinstance(v, type)}
387-
variables = {k: v for k, v in script_globals.items() if not callable(v)}
388-
print("Classes:", classes.keys()) # This lists a whole bunch of classes, including lib/mpos/ stuff
389-
print("Functions:", functions.keys())
390-
print("Variables:", variables.keys())
391-
main_activity = script_globals.get(classname)
392-
if main_activity:
393-
from mpos.activity_navigator import ActivityNavigator
394-
from .intent import Intent
351+
module_name = script_source.rsplit("/", 1)[-1]
352+
if "." in module_name:
353+
module_name = module_name.rsplit(".", 1)[0]
354+
previous_module = sys.modules.get(module_name, None)
355+
had_previous_module = module_name in sys.modules
356+
try:
357+
if had_previous_module:
358+
del sys.modules[module_name]
395359
start_time = utime.ticks_ms()
396-
ActivityNavigator.startActivity(
397-
Intent(activity_class=main_activity, app_fullname=app_fullname)
360+
module = __import__(module_name)
361+
import_time = utime.ticks_diff(utime.ticks_ms(), start_time)
362+
executed_name = getattr(module, "__file__", script_source)
363+
print(
364+
f"execute_script: importing module {module_name} took {import_time}ms"
398365
)
399-
end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
400-
print(f"execute_script: ActivityNavigator.startActivity took {end_time}ms")
401-
else:
402-
print(f"Warning: could not find app's main_activity {classname}")
366+
return _start_activity(getattr(module, classname, None), executed_name)
367+
except Exception as import_error:
368+
print(
369+
f"WARNING: failed importing app module {module_name} "
370+
f"from {compile_name}: {import_error}"
371+
)
372+
sys.print_exception(import_error)
403373
return False
374+
finally:
375+
if had_previous_module:
376+
sys.modules[module_name] = previous_module
377+
elif module_name in sys.modules:
378+
del sys.modules[module_name]
404379
except Exception as e:
405380
print(f"Thread {thread_id}: exception during execution:")
406381
sys.print_exception(e)
@@ -409,7 +384,6 @@ def execute_script(script_source, is_file, classname, cwd=None, app_fullname=Non
409384
# Always restore sys.path, even if we return early or raise an exception
410385
print(f"Thread {thread_id}: script {executed_name} finished, restoring sys.path from {sys.path} to {path_before}")
411386
sys.path = path_before
412-
return True
413387
except Exception as e:
414388
print(f"Thread {thread_id}: error:")
415389
import sys
@@ -437,7 +411,6 @@ def start_app(fullname):
437411
classname = app.main_launcher_activity.get("classname")
438412
result = AppManager.execute_script(
439413
app.installed_path + "/" + entrypoint,
440-
True,
441414
classname,
442415
app.installed_path + "/assets/",
443416
app_fullname=fullname,

internal_filesystem/lib/mpos/testing/mocks.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -888,7 +888,7 @@ def restart_launcher():
888888
return True
889889

890890
@staticmethod
891-
def execute_script(script_source, is_file, classname, cwd=None, app_fullname=None):
891+
def execute_script(script_source, classname, cwd=None, app_fullname=None):
892892
"""Mock execute_script function."""
893893
return True
894894

@@ -912,7 +912,7 @@ def restart_launcher():
912912
return True
913913

914914
@staticmethod
915-
def execute_script(script_source, is_file, classname, cwd=None, app_fullname=None):
915+
def execute_script(script_source, classname, cwd=None, app_fullname=None):
916916
"""Mock execute_script function."""
917917
return True
918918

@@ -1300,4 +1300,4 @@ def socket(af, sock_type):
13001300

13011301

13021302
def make_config_module(shared_prefs_cls):
1303-
return type("module", (), {"SharedPreferences": shared_prefs_cls})()
1303+
return type("module", (), {"SharedPreferences": shared_prefs_cls})()

tests/test_graphical_imu_calibration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def _start_activity_from_settings_assets(self, filename, classname):
3434
app_fullname = "com.micropythonos.settings"
3535
entrypoint = f"builtin/apps/{app_fullname}/assets/{filename}"
3636
cwd = f"builtin/apps/{app_fullname}/assets/"
37-
result = AppManager.execute_script(entrypoint, True, classname, cwd, app_fullname=app_fullname)
37+
result = AppManager.execute_script(entrypoint, classname, cwd, app_fullname=app_fullname)
3838
self.assertTrue(result, f"Failed to start {classname} from {entrypoint}")
3939
wait_for_render(iterations=60)
4040

tests/test_syspath_restore.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class TestSysPathRestore(unittest.TestCase):
66
"""Test that sys.path is properly restored after execute_script"""
77

88
def test_syspath_restored_after_execute_script(self):
9-
"""Test that sys.path is restored to original state after script execution"""
9+
"""Test that sys.path is restored to original state after file execution"""
1010
# Import here to ensure we're in the right context
1111
from mpos import AppManager
1212

@@ -15,29 +15,23 @@ def test_syspath_restored_after_execute_script(self):
1515
original_length = len(sys.path)
1616

1717
# Create a test directory path that would be added
18-
test_cwd = "apps/com.test.app/assets/"
18+
test_cwd = "builtin/apps/com.micropythonos.launcher/assets/"
1919

2020
# Verify the test path is not already in sys.path
2121
self.assertFalse(test_cwd in original_path,
2222
f"Test path {test_cwd} should not be in sys.path initially")
2323

24-
# Create a simple test script
25-
test_script = '''
26-
import sys
27-
# Just a simple script that does nothing
28-
x = 42
29-
'''
24+
test_script = "builtin/apps/com.micropythonos.launcher/assets/launcher.py"
3025

3126
# Call execute_script with cwd parameter
32-
# Note: This will fail because there's no Activity to start,
33-
# but that's fine - we're testing the sys.path restoration
3427
result = AppManager.execute_script(
3528
test_script,
36-
is_file=False,
37-
classname="NonExistentClass",
29+
classname="Launcher",
3830
cwd=test_cwd
3931
)
4032

33+
self.assertTrue(result)
34+
4135
# After execution, sys.path should be restored
4236
current_path = sys.path
4337
current_length = len(sys.path)
@@ -61,18 +55,17 @@ def test_syspath_not_affected_when_no_cwd(self):
6155
# Capture original sys.path
6256
original_path = sys.path[:]
6357

64-
test_script = '''
65-
x = 42
66-
'''
58+
test_script = "builtin/apps/com.micropythonos.launcher/assets/launcher.py"
6759

6860
# Call without cwd parameter
6961
result = AppManager.execute_script(
7062
test_script,
71-
is_file=False,
72-
classname="NonExistentClass",
63+
classname="Launcher",
7364
cwd=None
7465
)
7566

67+
self.assertFalse(result)
68+
7669
# sys.path should be unchanged
7770
self.assertEqual(sys.path, original_path,
7871
"sys.path should be unchanged when cwd is None")

0 commit comments

Comments
 (0)