pythonnative 0.8.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonnative/__init__.py +1 -1
- pythonnative/_ios_log.py +94 -0
- pythonnative/cli/pn.py +131 -11
- pythonnative/page.py +16 -1
- pythonnative/templates/ios_template/ios_template/ViewController.swift +19 -25
- pythonnative/utils.py +40 -1
- {pythonnative-0.8.0.dist-info → pythonnative-0.9.0.dist-info}/METADATA +1 -1
- {pythonnative-0.8.0.dist-info → pythonnative-0.9.0.dist-info}/RECORD +12 -11
- {pythonnative-0.8.0.dist-info → pythonnative-0.9.0.dist-info}/WHEEL +0 -0
- {pythonnative-0.8.0.dist-info → pythonnative-0.9.0.dist-info}/entry_points.txt +0 -0
- {pythonnative-0.8.0.dist-info → pythonnative-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {pythonnative-0.8.0.dist-info → pythonnative-0.9.0.dist-info}/top_level.txt +0 -0
pythonnative/__init__.py
CHANGED
pythonnative/_ios_log.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Route Python ``sys.stdout``/``sys.stderr`` through fd 2 on iOS.
|
|
2
|
+
|
|
3
|
+
Why
|
|
4
|
+
---
|
|
5
|
+
When an app is launched via ``xcrun simctl launch --console-pty`` (what
|
|
6
|
+
``pn run ios`` does), the simulator attaches the caller's terminal to
|
|
7
|
+
the app's stderr, which is the same channel ``NSLog`` / ``os_log``
|
|
8
|
+
writes to. Python ``print()`` calls, however, go to ``sys.stdout``
|
|
9
|
+
(fd 1), and for reasons specific to how CPython's embedded framework
|
|
10
|
+
is started on the iOS Simulator that descriptor does not reach the
|
|
11
|
+
attached console. As a result users see Swift-side ``NSLog`` output
|
|
12
|
+
but never their own ``print()`` output.
|
|
13
|
+
|
|
14
|
+
Redirecting ``sys.stdout`` / ``sys.stderr`` at a Python level to write
|
|
15
|
+
straight to fd 2 is a small, reliable fix: fd 2 *is* visible to
|
|
16
|
+
``simctl`` (that's exactly how ``NSLog`` reaches the terminal), so
|
|
17
|
+
Python output lands next to the Swift logs with correct ordering.
|
|
18
|
+
|
|
19
|
+
This module is intentionally self-contained: no rubicon-objc or
|
|
20
|
+
platform-specific C bindings required, so it's safe to import early
|
|
21
|
+
during ``pythonnative`` package initialization.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
from typing import Iterable
|
|
29
|
+
|
|
30
|
+
_STDERR_FD = 2
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _StderrStream:
|
|
34
|
+
"""Minimal text-mode file-like that writes UTF-8 bytes to fd 2.
|
|
35
|
+
|
|
36
|
+
It's write-through (no buffering) so a ``print()`` call appears in
|
|
37
|
+
the terminal immediately, which matches user expectations for an
|
|
38
|
+
interactive "run on simulator" log stream.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
encoding = "utf-8"
|
|
42
|
+
errors = "replace"
|
|
43
|
+
mode = "w"
|
|
44
|
+
name = "<stderr>"
|
|
45
|
+
|
|
46
|
+
def write(self, s: str) -> int:
|
|
47
|
+
if not s:
|
|
48
|
+
return 0
|
|
49
|
+
data = s.encode(self.encoding, self.errors)
|
|
50
|
+
try:
|
|
51
|
+
return os.write(_STDERR_FD, data)
|
|
52
|
+
except OSError:
|
|
53
|
+
return 0
|
|
54
|
+
|
|
55
|
+
def writelines(self, lines: Iterable[str]) -> None:
|
|
56
|
+
for line in lines:
|
|
57
|
+
self.write(line)
|
|
58
|
+
|
|
59
|
+
def flush(self) -> None:
|
|
60
|
+
# os.write is unbuffered; nothing to flush.
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def isatty(self) -> bool:
|
|
64
|
+
try:
|
|
65
|
+
return os.isatty(_STDERR_FD)
|
|
66
|
+
except OSError:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
def fileno(self) -> int:
|
|
70
|
+
return _STDERR_FD
|
|
71
|
+
|
|
72
|
+
def close(self) -> None:
|
|
73
|
+
# Don't actually close fd 2.
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def closed(self) -> bool:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
_installed = False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def install() -> None:
|
|
85
|
+
"""Swap ``sys.stdout`` / ``sys.stderr`` for fd-2 writers.
|
|
86
|
+
|
|
87
|
+
Safe to call multiple times; only the first call has effect.
|
|
88
|
+
"""
|
|
89
|
+
global _installed
|
|
90
|
+
if _installed:
|
|
91
|
+
return
|
|
92
|
+
sys.stdout = _StderrStream()
|
|
93
|
+
sys.stderr = _StderrStream()
|
|
94
|
+
_installed = True
|
pythonnative/cli/pn.py
CHANGED
|
@@ -246,6 +246,55 @@ def _read_requirements(requirements_path: str) -> list[str]:
|
|
|
246
246
|
return result
|
|
247
247
|
|
|
248
248
|
|
|
249
|
+
ANDROID_LOGCAT_FILTERS: list[str] = [
|
|
250
|
+
"python.stdout:V",
|
|
251
|
+
"python.stderr:V",
|
|
252
|
+
"MainActivity:V",
|
|
253
|
+
"PageFragment:V",
|
|
254
|
+
"Navigator:V",
|
|
255
|
+
"PythonNative:V",
|
|
256
|
+
"AndroidRuntime:E",
|
|
257
|
+
"System.err:W",
|
|
258
|
+
"*:S",
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
IOS_BUNDLE_ID: str = "com.pythonnative.ios-template"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _start_android_log_stream() -> Optional[subprocess.Popen]:
|
|
265
|
+
"""Clear logcat and stream Python-relevant log tags to the terminal.
|
|
266
|
+
|
|
267
|
+
Returns the ``adb logcat`` subprocess, or ``None`` when ``adb`` is
|
|
268
|
+
unavailable. Python's ``print()`` output reaches logcat via Chaquopy,
|
|
269
|
+
which redirects ``sys.stdout``/``sys.stderr`` to the ``python.stdout``
|
|
270
|
+
and ``python.stderr`` tags.
|
|
271
|
+
"""
|
|
272
|
+
try:
|
|
273
|
+
subprocess.run(["adb", "logcat", "-c"], check=False, capture_output=True)
|
|
274
|
+
except FileNotFoundError:
|
|
275
|
+
print("Note: 'adb' not found on PATH; skipping log streaming.")
|
|
276
|
+
return None
|
|
277
|
+
try:
|
|
278
|
+
proc = subprocess.Popen(["adb", "logcat", *ANDROID_LOGCAT_FILTERS])
|
|
279
|
+
except FileNotFoundError:
|
|
280
|
+
return None
|
|
281
|
+
print("Streaming Python logs from device (Ctrl+C to stop)...")
|
|
282
|
+
return proc
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _terminate_subprocess(proc: Optional[subprocess.Popen]) -> None:
|
|
286
|
+
"""Politely stop a subprocess, escalating to SIGKILL if needed."""
|
|
287
|
+
if proc is None:
|
|
288
|
+
return
|
|
289
|
+
if proc.poll() is not None:
|
|
290
|
+
return
|
|
291
|
+
proc.terminate()
|
|
292
|
+
try:
|
|
293
|
+
proc.wait(timeout=3)
|
|
294
|
+
except subprocess.TimeoutExpired:
|
|
295
|
+
proc.kill()
|
|
296
|
+
|
|
297
|
+
|
|
249
298
|
def run_project(args: argparse.Namespace) -> None:
|
|
250
299
|
"""
|
|
251
300
|
Run the specified project.
|
|
@@ -254,6 +303,7 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
254
303
|
platform: str = args.platform
|
|
255
304
|
prepare_only: bool = getattr(args, "prepare_only", False)
|
|
256
305
|
hot_reload: bool = getattr(args, "hot_reload", False)
|
|
306
|
+
show_logs: bool = not getattr(args, "no_logs", False)
|
|
257
307
|
|
|
258
308
|
# Read project configuration and save project root before any chdir
|
|
259
309
|
project_dir: str = os.getcwd()
|
|
@@ -371,6 +421,18 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
371
421
|
],
|
|
372
422
|
check=True,
|
|
373
423
|
)
|
|
424
|
+
|
|
425
|
+
# Stream Python logs from logcat unless the user opted out or requested
|
|
426
|
+
# hot-reload (hot-reload handles its own log tailing below).
|
|
427
|
+
if show_logs and not hot_reload:
|
|
428
|
+
logcat_proc = _start_android_log_stream()
|
|
429
|
+
if logcat_proc is not None:
|
|
430
|
+
try:
|
|
431
|
+
logcat_proc.wait()
|
|
432
|
+
except KeyboardInterrupt:
|
|
433
|
+
print()
|
|
434
|
+
_terminate_subprocess(logcat_proc)
|
|
435
|
+
print("Stopped log streaming.")
|
|
374
436
|
elif platform == "ios":
|
|
375
437
|
# Attempt to build and run on iOS Simulator (best-effort)
|
|
376
438
|
ios_project_dir: str = os.path.join(build_dir, "ios_template")
|
|
@@ -640,22 +702,64 @@ def run_project(args: argparse.Namespace) -> None:
|
|
|
640
702
|
return
|
|
641
703
|
|
|
642
704
|
udid = preferred.get("udid")
|
|
643
|
-
# Boot (no-op if already booted)
|
|
644
|
-
|
|
645
|
-
#
|
|
705
|
+
# Boot (no-op if already booted). simctl returns non-zero and
|
|
706
|
+
# prints to stderr when the device is already Booted; we
|
|
707
|
+
# don't care about that case, so swallow its output.
|
|
708
|
+
subprocess.run(["xcrun", "simctl", "boot", udid], check=False, capture_output=True)
|
|
709
|
+
# Install
|
|
646
710
|
subprocess.run(["xcrun", "simctl", "install", udid, app_path], check=False)
|
|
647
|
-
|
|
648
|
-
|
|
711
|
+
if show_logs and not hot_reload:
|
|
712
|
+
# Attach the app's stdout/stderr to this terminal so Python
|
|
713
|
+
# print() calls and exceptions are visible. SIMCTL_CHILD_*
|
|
714
|
+
# env vars are forwarded to the launched process.
|
|
715
|
+
sim_env = os.environ.copy()
|
|
716
|
+
sim_env["SIMCTL_CHILD_PYTHONUNBUFFERED"] = "1"
|
|
717
|
+
print("Launched iOS app on Simulator. Streaming logs (Ctrl+C to stop)...")
|
|
718
|
+
try:
|
|
719
|
+
subprocess.run(
|
|
720
|
+
[
|
|
721
|
+
"xcrun",
|
|
722
|
+
"simctl",
|
|
723
|
+
"launch",
|
|
724
|
+
"--console-pty",
|
|
725
|
+
"--terminate-running-process",
|
|
726
|
+
udid,
|
|
727
|
+
IOS_BUNDLE_ID,
|
|
728
|
+
],
|
|
729
|
+
env=sim_env,
|
|
730
|
+
check=False,
|
|
731
|
+
)
|
|
732
|
+
except KeyboardInterrupt:
|
|
733
|
+
print()
|
|
734
|
+
subprocess.run(
|
|
735
|
+
["xcrun", "simctl", "terminate", udid, IOS_BUNDLE_ID],
|
|
736
|
+
check=False,
|
|
737
|
+
capture_output=True,
|
|
738
|
+
)
|
|
739
|
+
print("Stopped log streaming.")
|
|
740
|
+
else:
|
|
741
|
+
subprocess.run(["xcrun", "simctl", "launch", udid, IOS_BUNDLE_ID], check=False)
|
|
742
|
+
print("Launched iOS app on Simulator (best-effort).")
|
|
743
|
+
if show_logs and hot_reload:
|
|
744
|
+
print(
|
|
745
|
+
"Note: live Python log streaming on iOS is disabled while --hot-reload is active; "
|
|
746
|
+
"use Console.app or Xcode to view logs."
|
|
747
|
+
)
|
|
649
748
|
except Exception:
|
|
650
749
|
print("Failed to auto-run on Simulator; open the project in Xcode to run.")
|
|
651
750
|
|
|
652
751
|
# Hot-reload file watcher
|
|
653
752
|
if hot_reload and not prepare_only:
|
|
654
|
-
_run_hot_reload(platform, project_dir, build_dir)
|
|
753
|
+
_run_hot_reload(platform, project_dir, build_dir, show_logs=show_logs)
|
|
754
|
+
|
|
655
755
|
|
|
756
|
+
def _run_hot_reload(platform: str, project_dir: str, build_dir: str, show_logs: bool = True) -> None:
|
|
757
|
+
"""Watch ``app/`` for changes and push updated files to the device.
|
|
656
758
|
|
|
657
|
-
|
|
658
|
-
|
|
759
|
+
When ``show_logs`` is true and targeting Android, ``adb logcat`` is
|
|
760
|
+
streamed in parallel so Python print/exception output stays visible
|
|
761
|
+
alongside hot-reload notifications.
|
|
762
|
+
"""
|
|
659
763
|
from .hot_reload import FileWatcher
|
|
660
764
|
|
|
661
765
|
app_dir = os.path.join(project_dir, "app")
|
|
@@ -673,12 +777,23 @@ def _run_hot_reload(platform: str, project_dir: str, build_dir: str) -> None:
|
|
|
673
777
|
print("[hot-reload] Watching app/ for changes. Press Ctrl+C to stop.")
|
|
674
778
|
watcher = FileWatcher(app_dir, on_change, interval=1.0)
|
|
675
779
|
watcher.start()
|
|
780
|
+
|
|
781
|
+
logcat_proc: Optional[subprocess.Popen] = None
|
|
782
|
+
if show_logs and platform == "android":
|
|
783
|
+
logcat_proc = _start_android_log_stream()
|
|
784
|
+
|
|
676
785
|
try:
|
|
677
|
-
|
|
786
|
+
if logcat_proc is not None:
|
|
787
|
+
logcat_proc.wait()
|
|
788
|
+
else:
|
|
789
|
+
import time
|
|
678
790
|
|
|
679
|
-
|
|
680
|
-
|
|
791
|
+
while True:
|
|
792
|
+
time.sleep(1)
|
|
681
793
|
except KeyboardInterrupt:
|
|
794
|
+
pass
|
|
795
|
+
finally:
|
|
796
|
+
_terminate_subprocess(logcat_proc)
|
|
682
797
|
watcher.stop()
|
|
683
798
|
print("\n[hot-reload] Stopped.")
|
|
684
799
|
|
|
@@ -721,6 +836,11 @@ def main() -> None:
|
|
|
721
836
|
action="store_true",
|
|
722
837
|
help="Watch app/ for changes and push updates to the running app",
|
|
723
838
|
)
|
|
839
|
+
parser_run.add_argument(
|
|
840
|
+
"--no-logs",
|
|
841
|
+
action="store_true",
|
|
842
|
+
help="Don't attach to the app's stdout/stderr after launching (default: stream logs)",
|
|
843
|
+
)
|
|
724
844
|
parser_run.set_defaults(func=run_project)
|
|
725
845
|
|
|
726
846
|
# Create a new command 'clean' that calls clean_project
|
pythonnative/page.py
CHANGED
|
@@ -27,7 +27,7 @@ import importlib
|
|
|
27
27
|
import json
|
|
28
28
|
from typing import Any, Dict, Optional
|
|
29
29
|
|
|
30
|
-
from .utils import IS_ANDROID, set_android_context
|
|
30
|
+
from .utils import IS_ANDROID, IS_IOS, set_android_context
|
|
31
31
|
|
|
32
32
|
_MAX_RENDER_PASSES = 25
|
|
33
33
|
|
|
@@ -250,6 +250,21 @@ else:
|
|
|
250
250
|
except ImportError:
|
|
251
251
|
pass
|
|
252
252
|
|
|
253
|
+
# Redirect Python's stdout/stderr through fd 2 so ``print()`` output is
|
|
254
|
+
# visible via ``xcrun simctl launch --console-pty``. This runs at
|
|
255
|
+
# ``pythonnative.page`` import time, i.e. before any user page module
|
|
256
|
+
# (e.g. ``app.main_page``) is imported, so their top-level ``print()``
|
|
257
|
+
# calls are captured too. Gated on ``IS_IOS`` rather than rubicon-objc
|
|
258
|
+
# being importable, so installing the ``[ios]`` extra on macOS does
|
|
259
|
+
# not silently swap ``sys.stdout`` on a dev machine.
|
|
260
|
+
if IS_IOS:
|
|
261
|
+
try:
|
|
262
|
+
from . import _ios_log
|
|
263
|
+
|
|
264
|
+
_ios_log.install()
|
|
265
|
+
except Exception:
|
|
266
|
+
pass
|
|
267
|
+
|
|
253
268
|
_IOS_PAGE_REGISTRY: _Dict[int, Any] = {}
|
|
254
269
|
|
|
255
270
|
def _ios_register_page(vc_instance: Any, host_obj: Any) -> None:
|
|
@@ -27,13 +27,18 @@ class ViewController: UIViewController {
|
|
|
27
27
|
super.viewDidLoad()
|
|
28
28
|
// Ensure a visible background when created programmatically (storyboards set this automatically)
|
|
29
29
|
view.backgroundColor = .systemBackground
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
30
|
+
|
|
31
|
+
let firstInit = !ViewController.hasInitializedPython
|
|
32
|
+
|
|
33
|
+
// Signal to pythonnative that we're running on iOS. Read on the
|
|
34
|
+
// Python side (pythonnative.utils.IS_IOS) to gate iOS-only setup
|
|
35
|
+
// like sys.stdout redirection. Set before Python starts so it's
|
|
36
|
+
// visible to the very first import.
|
|
37
|
+
setenv("PN_PLATFORM", "ios", 1)
|
|
38
|
+
|
|
39
|
+
// Configure embedded Python if available in bundle. PYTHONHOME /
|
|
40
|
+
// PYTHONPATH only need to be set once per process, but setting them
|
|
41
|
+
// again is harmless and keeps the flow simple.
|
|
37
42
|
if let resourcePath = Bundle.main.resourcePath {
|
|
38
43
|
let pyStd = "\(resourcePath)/python-stdlib"
|
|
39
44
|
let pyDyn = "\(resourcePath)/python-stdlib/lib-dynload"
|
|
@@ -44,8 +49,6 @@ class ViewController: UIViewController {
|
|
|
44
49
|
}
|
|
45
50
|
setenv("PYTHONHOME", pyStd, 1)
|
|
46
51
|
setenv("PYTHONPATH", pyPath, 1)
|
|
47
|
-
NSLog("[PN] Set PYTHONHOME=\(pyStd)")
|
|
48
|
-
NSLog("[PN] Set PYTHONPATH=\(pyPath)")
|
|
49
52
|
}
|
|
50
53
|
#if canImport(PythonKit)
|
|
51
54
|
// Ensure PythonKit knows where to load the Python library from when using an embedded framework.
|
|
@@ -53,34 +56,25 @@ class ViewController: UIViewController {
|
|
|
53
56
|
let frameworkLib = "\(bundlePath)/Frameworks/Python.framework/Python"
|
|
54
57
|
setenv("PYTHON_LIBRARY", frameworkLib, 1)
|
|
55
58
|
if FileManager.default.fileExists(atPath: frameworkLib) {
|
|
56
|
-
if
|
|
57
|
-
NSLog("[PN] Using embedded Python lib at: \(frameworkLib)")
|
|
59
|
+
if firstInit {
|
|
58
60
|
PythonLibrary.useLibrary(at: frameworkLib)
|
|
59
61
|
ViewController.hasInitializedPython = true
|
|
60
|
-
} else {
|
|
61
|
-
NSLog("[PN] Python library already initialized; skipping useLibrary")
|
|
62
62
|
}
|
|
63
63
|
pythonReady = true
|
|
64
64
|
} else {
|
|
65
65
|
NSLog("[PN] Embedded Python library not found at: \(frameworkLib)")
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
NSLog("[PN] PythonKit available; attempting Python bootstrap")
|
|
69
68
|
let sys = Python.import("sys")
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
if firstInit {
|
|
70
|
+
// One concise bootstrap line per process; per-page detail is left
|
|
71
|
+
// to Python-side print() statements streamed via pn run ios.
|
|
72
|
+
let shortVersion = "\(sys.version)".split(separator: "\n").first.map(String.init) ?? "\(sys.version)"
|
|
73
|
+
NSLog("[PN] Python \(shortVersion) initialized")
|
|
74
|
+
}
|
|
72
75
|
if let resourcePath = Bundle.main.resourcePath {
|
|
73
76
|
sys.path.append(resourcePath)
|
|
74
77
|
sys.path.append("\(resourcePath)/app")
|
|
75
|
-
NSLog("[PN] Updated sys.path: \(sys.path)")
|
|
76
|
-
// List bundled resources to verify Python files are present
|
|
77
|
-
let fm = FileManager.default
|
|
78
|
-
let appDir = "\(resourcePath)/app"
|
|
79
|
-
if let entries = try? fm.contentsOfDirectory(atPath: appDir) {
|
|
80
|
-
NSLog("[PN] Contents of /app in bundle: \(entries)")
|
|
81
|
-
} else {
|
|
82
|
-
NSLog("[PN] Could not list contents of \(appDir).")
|
|
83
|
-
}
|
|
84
78
|
}
|
|
85
79
|
// Determine which Python page to load
|
|
86
80
|
let pagePath: String = requestedPagePath ?? "app.main_page.MainPage"
|
pythonnative/utils.py
CHANGED
|
@@ -5,6 +5,7 @@ importing platform-specific packages at module level.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
import sys
|
|
8
9
|
from typing import Any, Optional
|
|
9
10
|
|
|
10
11
|
# ======================================================================
|
|
@@ -12,6 +13,7 @@ from typing import Any, Optional
|
|
|
12
13
|
# ======================================================================
|
|
13
14
|
|
|
14
15
|
_is_android: Optional[bool] = None
|
|
16
|
+
_is_ios: Optional[bool] = None
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
def _detect_android() -> bool:
|
|
@@ -27,10 +29,40 @@ def _detect_android() -> bool:
|
|
|
27
29
|
return False
|
|
28
30
|
|
|
29
31
|
|
|
32
|
+
def _detect_ios() -> bool:
|
|
33
|
+
"""Detect whether we're running inside an iOS app bundle.
|
|
34
|
+
|
|
35
|
+
Signals, in priority order:
|
|
36
|
+
|
|
37
|
+
- Explicit ``PN_PLATFORM=ios`` env var (set by the iOS template's
|
|
38
|
+
``ViewController.swift`` before Python starts). This is the
|
|
39
|
+
canonical signal and survives even on hosts where ``sys.platform``
|
|
40
|
+
is generic ``darwin``.
|
|
41
|
+
- ``sys.platform == "ios"`` (CPython 3.13+ native iOS builds).
|
|
42
|
+
- ``/CoreSimulator/Devices/`` in ``$HOME`` (iOS Simulator fallback
|
|
43
|
+
if the template signal is missing for some reason).
|
|
44
|
+
|
|
45
|
+
Crucially, having ``rubicon-objc`` importable is *not* enough:
|
|
46
|
+
developers frequently install it on macOS via the ``[ios]`` extra,
|
|
47
|
+
and treating that as iOS would cause subtle side effects
|
|
48
|
+
(e.g. stdout redirection) on desktop machines.
|
|
49
|
+
"""
|
|
50
|
+
if os.environ.get("PN_PLATFORM") == "ios":
|
|
51
|
+
return True
|
|
52
|
+
if sys.platform == "ios":
|
|
53
|
+
return True
|
|
54
|
+
home = os.environ.get("HOME", "")
|
|
55
|
+
if "/CoreSimulator/Devices/" in home:
|
|
56
|
+
return True
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
30
60
|
def _ensure_platform_detection() -> None:
|
|
31
|
-
global _is_android
|
|
61
|
+
global _is_android, _is_ios
|
|
32
62
|
if _is_android is None:
|
|
33
63
|
_is_android = _detect_android()
|
|
64
|
+
if _is_ios is None:
|
|
65
|
+
_is_ios = (not _is_android) and _detect_ios()
|
|
34
66
|
|
|
35
67
|
|
|
36
68
|
def _get_is_android() -> bool:
|
|
@@ -39,7 +71,14 @@ def _get_is_android() -> bool:
|
|
|
39
71
|
return _is_android
|
|
40
72
|
|
|
41
73
|
|
|
74
|
+
def _get_is_ios() -> bool:
|
|
75
|
+
_ensure_platform_detection()
|
|
76
|
+
assert _is_ios is not None
|
|
77
|
+
return _is_ios
|
|
78
|
+
|
|
79
|
+
|
|
42
80
|
IS_ANDROID: bool = _get_is_android()
|
|
81
|
+
IS_IOS: bool = _get_is_ios()
|
|
43
82
|
|
|
44
83
|
# ======================================================================
|
|
45
84
|
# Android context management
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
pythonnative/__init__.py,sha256=
|
|
1
|
+
pythonnative/__init__.py,sha256=zKplWL38v3kFMeQ8Q2VqnjF-nAM5vx3EW8UIKqJddDo,2038
|
|
2
|
+
pythonnative/_ios_log.py,sha256=dkTSUeOF9xRQjgxMYdH6EohjzjmeInwOfr-_0tkBmpE,2639
|
|
2
3
|
pythonnative/components.py,sha256=zF24vXM6halq-fweLtLHxZrmBrUKCa9c44hf9Bych-Y,12767
|
|
3
4
|
pythonnative/element.py,sha256=RBUsXzzzM7KdK-NqMD-InVPKdAb8XJ0h0VpI2rwsfHs,1795
|
|
4
5
|
pythonnative/hooks.py,sha256=bqnJvEpbYSlbwkBKphT5lLEXQ1mO6AlO4r9SCTBlu7w,13768
|
|
5
6
|
pythonnative/hot_reload.py,sha256=dtppJaQI6Rl7muCTMgLjFNsD4_F4yYnkEpliCLaaWm8,4508
|
|
6
7
|
pythonnative/navigation.py,sha256=dDY9qBPLJKr2NnvdIDs8Xy-6kFesQNCEaLjUDT-0_l8,19042
|
|
7
|
-
pythonnative/page.py,sha256=
|
|
8
|
+
pythonnative/page.py,sha256=J1mC6jVAu6IjFSwDGPU3cMfiXLPHoW2RbyZ1kj3OSfk,16814
|
|
8
9
|
pythonnative/reconciler.py,sha256=EuB8InxqlbZpZ0qI8hSjwNQsS-lsvh0vdqGzTnmY1M8,14133
|
|
9
10
|
pythonnative/style.py,sha256=NG58FSJCBTBBWRrDOyUiHZsTxQDA3jg2PSOcCsU2F5g,3882
|
|
10
|
-
pythonnative/utils.py,sha256=
|
|
11
|
+
pythonnative/utils.py,sha256=dp-cOYSflWvsY9Qh851OeBq-x6S8W1cx4nyjDxlsd0M,3859
|
|
11
12
|
pythonnative/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
pythonnative/cli/pn.py,sha256=
|
|
13
|
+
pythonnative/cli/pn.py,sha256=KGDbxRYtZHUS4rId_Br6oI7kEGl0pcRAo5UGztUvTN4,37478
|
|
13
14
|
pythonnative/native_modules/__init__.py,sha256=M_4SW-wCZ8azLuB0JLwUc8PESw00xMCM887-HuN9YNo,647
|
|
14
15
|
pythonnative/native_modules/camera.py,sha256=b3UErkABhBm2nQ-e72lEeFOgXoVDY6245cV6TN9iPBk,3509
|
|
15
16
|
pythonnative/native_modules/file_system.py,sha256=2fvYsIboCtjEyxmVeV72glOjtc9fJN3jTM6uaccru6E,4478
|
|
@@ -59,7 +60,7 @@ pythonnative/templates/android_template/gradle/wrapper/gradle-wrapper.properties
|
|
|
59
60
|
pythonnative/templates/ios_template/ios_template/AppDelegate.swift,sha256=_6G8GNcw4idXd75qKgQKTDCr45Ez73QB8WTvhBqqcMw,1349
|
|
60
61
|
pythonnative/templates/ios_template/ios_template/Info.plist,sha256=ZQIJGpo8Y2qP0j29xqOsIEGvPpEVICLTAw2NehC5CSo,704
|
|
61
62
|
pythonnative/templates/ios_template/ios_template/SceneDelegate.swift,sha256=lqtre92dc6d6s-f4ieh_M_4xmc_zMGW79j46tDu9cOY,2177
|
|
62
|
-
pythonnative/templates/ios_template/ios_template/ViewController.swift,sha256=
|
|
63
|
+
pythonnative/templates/ios_template/ios_template/ViewController.swift,sha256=5bVNJ4TT6GIaICzVxl8hgcFmBgaPqjiCivlJzFy0p7c,7920
|
|
63
64
|
pythonnative/templates/ios_template/ios_template/Assets.xcassets/Contents.json,sha256=D9Sbo8NYXHCWeOAEaoIcPGBoXscGNyDTDTo0SL46IIs,63
|
|
64
65
|
pythonnative/templates/ios_template/ios_template/Assets.xcassets/AccentColor.colorset/Contents.json,sha256=mvZQhvowtJJS-uGhIlcxaR3nlPd3WvdNcb7-tQfRK3w,123
|
|
65
66
|
pythonnative/templates/ios_template/ios_template/Assets.xcassets/AppIcon.appiconset/Contents.json,sha256=VUwGr7K_geOvQjFh5VKB6iVXV1mi0tjGMinUmB2JvQs,177
|
|
@@ -70,9 +71,9 @@ pythonnative/templates/ios_template/ios_template.xcodeproj/project.xcworkspace/x
|
|
|
70
71
|
pythonnative/templates/ios_template/ios_templateTests/ios_templateTests.swift,sha256=YnwzZx7yXB13xKAXEGNgz17VuhWeqkHTRTtBJ2Vu3_E,1238
|
|
71
72
|
pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITests.swift,sha256=l2Pwa50F_rv-qPu2go6e4bQernM6PTQJeNPFl_c4ivY,1387
|
|
72
73
|
pythonnative/templates/ios_template/ios_templateUITests/ios_templateUITestsLaunchTests.swift,sha256=f5JrG0uVtLMeJQy26Yyz7Om-JUkT220osqcbeIVkj2g,815
|
|
73
|
-
pythonnative-0.
|
|
74
|
-
pythonnative-0.
|
|
75
|
-
pythonnative-0.
|
|
76
|
-
pythonnative-0.
|
|
77
|
-
pythonnative-0.
|
|
78
|
-
pythonnative-0.
|
|
74
|
+
pythonnative-0.9.0.dist-info/licenses/LICENSE,sha256=A69iG7TIAe6KkGQf6xoVHkc5JSZtOr5eRSvC5iuivnI,1067
|
|
75
|
+
pythonnative-0.9.0.dist-info/METADATA,sha256=YOu6Me-TqLtHWoWkL8A8UWp6WY9f-OzzkSfXZp8X86s,6692
|
|
76
|
+
pythonnative-0.9.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
77
|
+
pythonnative-0.9.0.dist-info/entry_points.txt,sha256=iUtDawWSAJAEyWTycpZxDuYz73ol31butpzDIEAgPO0,48
|
|
78
|
+
pythonnative-0.9.0.dist-info/top_level.txt,sha256=kT4SEATY2ywzrZ2Pgea6_zxyym44Q_PbOsUoOYjJLFE,13
|
|
79
|
+
pythonnative-0.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|