Skip to content

Commit 4d40ceb

Browse files
committed
Compile Python app and dependencies to bytecode
1 parent cde00d5 commit 4d40ceb

File tree

6 files changed

+69
-38
lines changed

6 files changed

+69
-38
lines changed

.appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ build_script:
4646
- ls dist
4747
- sh: |
4848
DIST_FILE_NAME=dist/python-ios-dist-v$APPVEYOR_BUILD_VERSION.tar.gz
49-
tar -czvf $DIST_FILE_NAME dist/lib dist/root
49+
tar -czvf $DIST_FILE_NAME dist/*
5050
appveyor PushArtifact $DIST_FILE_NAME -DeploymentName python-dist
5151
5252
# publish package

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## 0.1.3
22

33
* Add app's path to `PYTHONPATH`.
4+
* Compile Python app to bytecode.
45

56
## 0.1.2
67

bin/package_command.dart

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import 'dart:convert';
22
import 'dart:io';
3+
import 'dart:isolate';
34

45
import 'package:archive/archive_io.dart';
56
import 'package:args/command_runner.dart';
67
import 'package:path/path.dart' as path;
78
import 'package:toml/toml.dart';
89

10+
const junkFileExtensions = [".so", ".py", ".c", ".typed"];
11+
const junkFilesAndDirectories = ["__pycache__", "bin"];
12+
913
class PackageCommand extends Command {
1014
@override
1115
final name = "package";
@@ -37,6 +41,17 @@ class PackageCommand extends Command {
3741

3842
try {
3943
final currentPath = Directory.current.path;
44+
final packagePath = (await Isolate.resolvePackageUri(
45+
Uri.parse('package:serious_python/.')))
46+
?.path;
47+
48+
if (packagePath == null) {
49+
stdout.writeln("Cannot resolve 'serious_python' package path.");
50+
exit(1);
51+
}
52+
53+
final iosDistPath =
54+
path.join(Directory(packagePath).parent.absolute.path, "ios", "dist");
4055

4156
// source dir
4257
var sourceDirPath = argResults!.rest.first;
@@ -111,36 +126,28 @@ class PackageCommand extends Command {
111126
extraArgs.add("--pre");
112127
}
113128

114-
final pipProcess = await Process.start(
115-
'python3',
116-
[
117-
'-m',
118-
'pip',
119-
'install',
120-
'--isolated',
121-
'--upgrade',
122-
...extraArgs,
123-
'--target',
124-
path.join(tempDir.path, '__pypackages__'),
125-
...dependencies
126-
],
127-
environment: {
128-
"CC": "/bin/false",
129-
"CXX": "/bin/false",
130-
"PYTHONPATH": tempDir.path,
131-
"PYTHONOPTIMIZE": "2",
132-
},
133-
runInShell: true,
134-
includeParentEnvironment: false);
135-
136-
await for (final line in pipProcess.stdout.transform(utf8.decoder)) {
137-
stdout.write(line);
138-
}
139-
140-
if (await pipProcess.exitCode != 0) {
141-
stdout.write(await pipProcess.stderr.transform(utf8.decoder).join());
142-
exit(1);
143-
}
129+
final pythonPath =
130+
path.join(iosDistPath, "hostpython3", "bin", "python");
131+
132+
await runExec(pythonPath, [
133+
'-m',
134+
'pip',
135+
'install',
136+
'--isolated',
137+
'--upgrade',
138+
...extraArgs,
139+
'--target',
140+
path.join(tempDir.path, '__pypackages__'),
141+
...dependencies
142+
], environment: {
143+
"CC": "/bin/false",
144+
"CXX": "/bin/false",
145+
"PYTHONPATH": tempDir.path,
146+
"PYTHONOPTIMIZE": "2",
147+
});
148+
149+
// compile all python code
150+
await runExec(pythonPath, ['-m', 'compileall', '-b', tempDir.path]);
144151
}
145152

146153
// remove unnecessary files
@@ -193,17 +200,38 @@ class PackageCommand extends Command {
193200
directory.listSync().forEach((entity) {
194201
if (entity is Directory) {
195202
cleanupPyPackages(entity);
196-
} else if (entity is File && entity.path.endsWith('.so')) {
203+
} else if (entity is File &&
204+
junkFileExtensions.contains(path.extension(entity.path)) ||
205+
junkFilesAndDirectories.contains(path.basename(entity.path))) {
206+
stdout.writeln("Deleting ${entity.path}");
197207
entity.deleteSync();
198208
}
199209
});
200210

201211
directory.listSync().forEach((entity) {
202-
if (entity is Directory && entity.path.endsWith('__pycache__')) {
203-
entity.deleteSync(recursive: true);
204-
} else if (entity is Directory && entity.path.endsWith('bin')) {
212+
if (entity is Directory &&
213+
junkFilesAndDirectories.contains(path.basename(entity.path))) {
214+
stdout.writeln("Deleting ${entity.path}");
205215
entity.deleteSync(recursive: true);
206216
}
207217
});
208218
}
219+
220+
Future<int> runExec(String execPath, List<String> args,
221+
{Map<String, String>? environment}) async {
222+
final proc = await Process.start(execPath, args,
223+
environment: environment,
224+
runInShell: true,
225+
includeParentEnvironment: false);
226+
227+
await for (final line in proc.stdout.transform(utf8.decoder)) {
228+
stdout.write(line);
229+
}
230+
231+
if (await proc.exitCode != 0) {
232+
stdout.write(await proc.stderr.transform(utf8.decoder).join());
233+
exit(1);
234+
}
235+
return proc.exitCode;
236+
}
209237
}

example/flask_example/app/src/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from flask import Flask, request
55

6-
print("Python program has started!")
6+
print("Python program has just started!")
77

88

99
class PythonRunner:

example/flask_example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ PODS:
33
- path_provider_foundation (0.0.1):
44
- Flutter
55
- FlutterMacOS
6-
- serious_python (0.1.0):
6+
- serious_python (0.1.2):
77
- Flutter
88

99
DEPENDENCIES:
@@ -22,7 +22,7 @@ EXTERNAL SOURCES:
2222
SPEC CHECKSUMS:
2323
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
2424
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
25-
serious_python: 954ab1502631a446dd28e0bacdb519e8a803a1fe
25+
serious_python: 11f4ce79371037a06e96c78c8ed9b30cd7d0b6f7
2626

2727
PODFILE CHECKSUM: 7be2f5f74864d463a8ad433546ed1de7e0f29aef
2828

lib/serious_python.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class SeriousPython {
4747
appPath = await extractAssetZip(assetPath);
4848
if (appFileName != null) {
4949
appPath = p.join(appPath, appFileName);
50+
} else if (await File(p.join(appPath, "main.pyc")).exists()) {
51+
appPath = p.join(appPath, "main.pyc");
5052
} else if (await File(p.join(appPath, "main.py")).exists()) {
5153
appPath = p.join(appPath, "main.py");
5254
} else {

0 commit comments

Comments
 (0)