@@ -4,6 +4,8 @@ import 'dart:io';
4
4
import 'package:archive/archive_io.dart' ;
5
5
import 'package:args/command_runner.dart' ;
6
6
import 'package:crypto/crypto.dart' ;
7
+ import 'package:glob/glob.dart' ;
8
+ import 'package:glob/list_local_fs.dart' ;
7
9
import 'package:http/http.dart' as http;
8
10
import 'package:path/path.dart' as path;
9
11
import 'package:shelf/shelf.dart' ;
@@ -13,11 +15,11 @@ import 'macos_utils.dart' as macos_utils;
13
15
import 'sitecustomize.dart' ;
14
16
15
17
const mobilePyPiUrl = "https://pypi.flet.dev" ;
16
- const pyodideRootUrl = "https://cdn.jsdelivr.net/pyodide/v0.26 .2/full" ;
18
+ const pyodideRootUrl = "https://cdn.jsdelivr.net/pyodide/v0.27 .2/full" ;
17
19
const pyodideLockFile = "pyodide-lock.json" ;
18
20
19
- const buildPythonVersion = "3.12.6 " ;
20
- const buildPythonReleaseDate = "20240909 " ;
21
+ const buildPythonVersion = "3.12.9 " ;
22
+ const buildPythonReleaseDate = "20250205 " ;
21
23
const defaultSitePackagesDir = "__pypackages__" ;
22
24
const sitePackagesEnvironmentVariable = "SERIOUS_PYTHON_SITE_PACKAGES" ;
23
25
const flutterPackagesFlutterEnvironmentVariable =
@@ -58,11 +60,28 @@ const platforms = {
58
60
}
59
61
};
60
62
61
- const junkFileExtensionsDesktop = [".c" , ".h" , ".hpp" , ".typed" , ".a" , ".pdb" ];
62
- const junkFileExtensionsMobile = [...junkFileExtensionsDesktop, ".exe" , ".dll" ];
63
-
64
- const junkFilesDesktop = ["__pycache__" ];
65
- const junkFilesMobile = [...junkFilesDesktop, "bin" ];
63
+ const junkFilesDesktop = [
64
+ "**.c" ,
65
+ "**.h" ,
66
+ "**.cpp" ,
67
+ "**.hpp" ,
68
+ "**.typed" ,
69
+ "**.pyi" ,
70
+ "**.pxd" ,
71
+ "**.pyx" ,
72
+ "**.a" ,
73
+ "**.pdb" ,
74
+ "**.dist-info" ,
75
+ "__pycache__" ,
76
+ "**/__pycache__" ,
77
+ ];
78
+ const junkFilesMobile = [
79
+ ...junkFilesDesktop,
80
+ "**.exe" ,
81
+ "**.dll" ,
82
+ "bin" ,
83
+ "**/bin" ,
84
+ ];
66
85
67
86
class PackageCommand extends Command {
68
87
bool _verbose = false ;
@@ -104,6 +123,16 @@ class PackageCommand extends Command {
104
123
help:
105
124
"Cleanup app and packages from unneccessary files and directories." ,
106
125
negatable: false );
126
+ argParser.addFlag ("cleanup-app" ,
127
+ help: "Cleanup app from unneccessary files and directories." ,
128
+ negatable: false );
129
+ argParser.addMultiOption ('cleanup-app-files' ,
130
+ help: "List of globs to delete extra app files and directories." );
131
+ argParser.addFlag ("cleanup-packages" ,
132
+ help: "Cleanup packages from unneccessary files and directories." ,
133
+ negatable: false );
134
+ argParser.addMultiOption ('cleanup-packages-files' ,
135
+ help: "List of globs to delete extra packages files and directories." );
107
136
argParser.addFlag ("verbose" , help: "Verbose output." , negatable: false );
108
137
}
109
138
@@ -135,6 +164,10 @@ class PackageCommand extends Command {
135
164
bool compileApp = argResults? ["compile-app" ];
136
165
bool compilePackages = argResults? ["compile-packages" ];
137
166
bool cleanup = argResults? ["cleanup" ];
167
+ bool cleanupApp = argResults? ["cleanup-app" ];
168
+ List <String > cleanupAppFiles = argResults? ['cleanup-app-files' ];
169
+ bool cleanupPackages = argResults? ["cleanup-packages" ];
170
+ List <String > cleanupPackagesFiles = argResults? ['cleanup-packages-files' ];
138
171
_verbose = argResults? ["verbose" ];
139
172
140
173
if (path.isRelative (sourceDirPath)) {
@@ -162,8 +195,6 @@ class PackageCommand extends Command {
162
195
bool isMobile = (platform == "iOS" || platform == "Android" );
163
196
bool isWeb = platform == "Pyodide" ;
164
197
165
- var junkFileExtensions =
166
- isMobile ? junkFileExtensionsMobile : junkFileExtensionsDesktop;
167
198
var junkFiles = isMobile ? junkFilesMobile : junkFilesDesktop;
168
199
169
200
// Extra indexs
@@ -212,19 +243,19 @@ class PackageCommand extends Command {
212
243
await runPython (['-m' , 'compileall' , '-b' , tempDir.path]);
213
244
214
245
verbose ("Deleting original .py files" );
215
- await cleanupPyPackages (tempDir, [".py" ], [ ]);
246
+ await cleanupDir (tempDir, ["** .py" ]);
216
247
}
217
248
218
249
// cleanup
219
- if (cleanup) {
250
+ if (cleanupApp || cleanup) {
251
+ var allJunkFiles = [...junkFiles, ...cleanupAppFiles];
220
252
if (_verbose) {
221
253
verbose (
222
- "Delete unnecessary app files with extensions: $junkFileExtensions " );
223
- verbose ("Delete unnecessary app files and directories: $junkFiles " );
254
+ "Delete unnecessary app files and directories: $allJunkFiles " );
224
255
} else {
225
256
stdout.writeln (("Cleanup app" ));
226
257
}
227
- await cleanupPyPackages (tempDir, junkFileExtensions, junkFiles );
258
+ await cleanupDir (tempDir, allJunkFiles );
228
259
}
229
260
230
261
// install requirements
@@ -358,21 +389,19 @@ class PackageCommand extends Command {
358
389
await runPython (['-m' , 'compileall' , '-b' , sitePackagesDir]);
359
390
360
391
verbose ("Deleting original .py files" );
361
- await cleanupPyPackages (Directory (sitePackagesDir), [".py" ], [ ]);
392
+ await cleanupDir (Directory (sitePackagesDir), ["** .py" ]);
362
393
}
363
394
364
395
// cleanup packages
365
- if (cleanup) {
396
+ if (cleanupPackages || cleanup) {
397
+ var allJunkFiles = [...junkFiles, ...cleanupPackagesFiles];
366
398
if (_verbose) {
367
399
verbose (
368
- "Delete unnecessary package files with extensions: $junkFileExtensions " );
369
- verbose (
370
- "Delete unnecessary package files and directories: $junkFiles " );
400
+ "Delete unnecessary package files and directories: $allJunkFiles " );
371
401
} else {
372
402
stdout.writeln (("Cleanup installed packages" ));
373
403
}
374
- await cleanupPyPackages (
375
- Directory (sitePackagesDir), junkFileExtensions, junkFiles);
404
+ await cleanupDir (Directory (sitePackagesDir), allJunkFiles);
376
405
}
377
406
} finally {
378
407
if (sitecustomizeDir != null && await sitecustomizeDir.exists ()) {
@@ -435,28 +464,34 @@ class PackageCommand extends Command {
435
464
}
436
465
}
437
466
438
- Future <void > cleanupPyPackages (Directory directory,
439
- List <String > fileExtensions, List <String > filesAndDirectories) async {
440
- await for (var entity in directory.list ()) {
441
- if (entity is Directory ) {
442
- await cleanupPyPackages (entity, fileExtensions, filesAndDirectories);
443
- } else if (entity is File &&
444
- (fileExtensions.contains (path.extension (entity.path)) ||
445
- filesAndDirectories.contains (path.basename (entity.path)))) {
446
- verbose ("Deleting ${entity .path }" );
447
-
448
- await entity.delete ();
449
- }
450
- }
467
+ Future <void > cleanupDir (Directory directory, List <String > filesGlobs) async {
468
+ verbose ("Cleanup directory ${directory .path }: $filesGlobs " );
469
+ await cleanupDirRecursive (
470
+ directory,
471
+ filesGlobs.map ((g) => Glob (g.replaceAll ("\\ " , "/" ),
472
+ context: path.Context (current: directory.path))));
473
+ }
451
474
452
- await for (var entity in directory.list ()) {
453
- if (entity is Directory &&
454
- filesAndDirectories.contains (path.basename (entity.path))) {
475
+ Future <bool > cleanupDirRecursive (
476
+ Directory directory, Iterable <Glob > globs) async {
477
+ var emptyDir = true ;
478
+ for (var entity in directory.listSync ()) {
479
+ if (globs.any ((g) => g.matches (entity.path.replaceAll ("\\ " , "/" ))) &&
480
+ await entity.exists ()) {
455
481
verbose ("Deleting ${entity .path }" );
456
-
457
482
await entity.delete (recursive: true );
483
+ } else if (entity is Directory ) {
484
+ if (await cleanupDirRecursive (entity, globs)) {
485
+ verbose ("Deleting empty directory ${entity .path }" );
486
+ await entity.delete (recursive: true );
487
+ } else {
488
+ emptyDir = false ;
489
+ }
490
+ } else {
491
+ emptyDir = false ;
458
492
}
459
493
}
494
+ return emptyDir;
460
495
}
461
496
462
497
Future <int > runExec (String execPath, List <String > args,
@@ -507,7 +542,7 @@ class PackageCommand extends Command {
507
542
if (! await File (pythonArchivePath).exists ()) {
508
543
// download Python distr from GitHub
509
544
final url =
510
- "https://github.com/indygreg /python-build-standalone/releases/download/$buildPythonReleaseDate /$pythonArchiveFilename " ;
545
+ "https://github.com/astral-sh /python-build-standalone/releases/download/$buildPythonReleaseDate /$pythonArchiveFilename " ;
511
546
512
547
if (_verbose) {
513
548
verbose (
@@ -523,13 +558,18 @@ class PackageCommand extends Command {
523
558
524
559
// extract Python from archive
525
560
if (_verbose) {
526
- "Extracting Python distributive from $pythonArchivePath to ${_pythonDir !.path }" ;
561
+ verbose (
562
+ "Extracting Python distributive from $pythonArchivePath to ${_pythonDir !.path }" );
527
563
} else {
528
564
stdout.writeln ("Extracting Python distributive" );
529
565
}
530
566
531
567
await Process .run (
532
568
'tar' , ['-xzf' , pythonArchivePath, '-C' , _pythonDir! .path]);
569
+
570
+ if (Platform .isMacOS) {
571
+ duplicateSysconfigFile (_pythonDir! .path);
572
+ }
533
573
}
534
574
}
535
575
@@ -542,6 +582,28 @@ class PackageCommand extends Command {
542
582
return await runExec (pythonExePath, args, environment: environment);
543
583
}
544
584
585
+ void duplicateSysconfigFile (String pythonDir) {
586
+ final sysConfigGlob = Glob ("python/lib/python3.*/_sysconfigdata__*.py" ,
587
+ context: path.Context (current: pythonDir));
588
+ for (var sysConfig in sysConfigGlob.listSync (root: pythonDir)) {
589
+ // copy the first found sys config and exit
590
+ if (sysConfig is File ) {
591
+ for (final target in [
592
+ '_sysconfigdata__darwin_arm64_iphoneos.py' ,
593
+ '_sysconfigdata__darwin_arm64_iphonesimulator.py' ,
594
+ '_sysconfigdata__darwin_x86_64_iphonesimulator.py' ,
595
+ ]) {
596
+ var targetPath = path.join (sysConfig.parent.path, target);
597
+ (sysConfig as File ).copySync (targetPath);
598
+ if (_verbose) {
599
+ verbose ('Copied ${sysConfig .path } -> $targetPath ' );
600
+ }
601
+ }
602
+ break ;
603
+ }
604
+ }
605
+ }
606
+
545
607
Future <HttpServer > startSimpleServer () async {
546
608
const htmlHeader = "<!DOCTYPE html><html><body>\n " ;
547
609
const htmlFooter = "</body></html>\n " ;
0 commit comments