@@ -4,6 +4,8 @@ import 'dart:io';
44import 'package:archive/archive_io.dart' ;
55import 'package:args/command_runner.dart' ;
66import 'package:crypto/crypto.dart' ;
7+ import 'package:glob/glob.dart' ;
8+ import 'package:glob/list_local_fs.dart' ;
79import 'package:http/http.dart' as http;
810import 'package:path/path.dart' as path;
911import 'package:shelf/shelf.dart' ;
@@ -13,11 +15,11 @@ import 'macos_utils.dart' as macos_utils;
1315import 'sitecustomize.dart' ;
1416
1517const 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" ;
1719const pyodideLockFile = "pyodide-lock.json" ;
1820
19- const buildPythonVersion = "3.12.6 " ;
20- const buildPythonReleaseDate = "20240909 " ;
21+ const buildPythonVersion = "3.12.9 " ;
22+ const buildPythonReleaseDate = "20250205 " ;
2123const defaultSitePackagesDir = "__pypackages__" ;
2224const sitePackagesEnvironmentVariable = "SERIOUS_PYTHON_SITE_PACKAGES" ;
2325const flutterPackagesFlutterEnvironmentVariable =
@@ -58,11 +60,28 @@ const platforms = {
5860 }
5961};
6062
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+ ];
6685
6786class PackageCommand extends Command {
6887 bool _verbose = false ;
@@ -104,6 +123,16 @@ class PackageCommand extends Command {
104123 help:
105124 "Cleanup app and packages from unneccessary files and directories." ,
106125 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." );
107136 argParser.addFlag ("verbose" , help: "Verbose output." , negatable: false );
108137 }
109138
@@ -135,6 +164,10 @@ class PackageCommand extends Command {
135164 bool compileApp = argResults? ["compile-app" ];
136165 bool compilePackages = argResults? ["compile-packages" ];
137166 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' ];
138171 _verbose = argResults? ["verbose" ];
139172
140173 if (path.isRelative (sourceDirPath)) {
@@ -162,8 +195,6 @@ class PackageCommand extends Command {
162195 bool isMobile = (platform == "iOS" || platform == "Android" );
163196 bool isWeb = platform == "Pyodide" ;
164197
165- var junkFileExtensions =
166- isMobile ? junkFileExtensionsMobile : junkFileExtensionsDesktop;
167198 var junkFiles = isMobile ? junkFilesMobile : junkFilesDesktop;
168199
169200 // Extra indexs
@@ -212,19 +243,19 @@ class PackageCommand extends Command {
212243 await runPython (['-m' , 'compileall' , '-b' , tempDir.path]);
213244
214245 verbose ("Deleting original .py files" );
215- await cleanupPyPackages (tempDir, [".py" ], [ ]);
246+ await cleanupDir (tempDir, ["** .py" ]);
216247 }
217248
218249 // cleanup
219- if (cleanup) {
250+ if (cleanupApp || cleanup) {
251+ var allJunkFiles = [...junkFiles, ...cleanupAppFiles];
220252 if (_verbose) {
221253 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 " );
224255 } else {
225256 stdout.writeln (("Cleanup app" ));
226257 }
227- await cleanupPyPackages (tempDir, junkFileExtensions, junkFiles );
258+ await cleanupDir (tempDir, allJunkFiles );
228259 }
229260
230261 // install requirements
@@ -358,21 +389,19 @@ class PackageCommand extends Command {
358389 await runPython (['-m' , 'compileall' , '-b' , sitePackagesDir]);
359390
360391 verbose ("Deleting original .py files" );
361- await cleanupPyPackages (Directory (sitePackagesDir), [".py" ], [ ]);
392+ await cleanupDir (Directory (sitePackagesDir), ["** .py" ]);
362393 }
363394
364395 // cleanup packages
365- if (cleanup) {
396+ if (cleanupPackages || cleanup) {
397+ var allJunkFiles = [...junkFiles, ...cleanupPackagesFiles];
366398 if (_verbose) {
367399 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 " );
371401 } else {
372402 stdout.writeln (("Cleanup installed packages" ));
373403 }
374- await cleanupPyPackages (
375- Directory (sitePackagesDir), junkFileExtensions, junkFiles);
404+ await cleanupDir (Directory (sitePackagesDir), allJunkFiles);
376405 }
377406 } finally {
378407 if (sitecustomizeDir != null && await sitecustomizeDir.exists ()) {
@@ -435,28 +464,34 @@ class PackageCommand extends Command {
435464 }
436465 }
437466
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+ }
451474
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 ()) {
455481 verbose ("Deleting ${entity .path }" );
456-
457482 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 ;
458492 }
459493 }
494+ return emptyDir;
460495 }
461496
462497 Future <int > runExec (String execPath, List <String > args,
@@ -507,7 +542,7 @@ class PackageCommand extends Command {
507542 if (! await File (pythonArchivePath).exists ()) {
508543 // download Python distr from GitHub
509544 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 " ;
511546
512547 if (_verbose) {
513548 verbose (
@@ -523,13 +558,18 @@ class PackageCommand extends Command {
523558
524559 // extract Python from archive
525560 if (_verbose) {
526- "Extracting Python distributive from $pythonArchivePath to ${_pythonDir !.path }" ;
561+ verbose (
562+ "Extracting Python distributive from $pythonArchivePath to ${_pythonDir !.path }" );
527563 } else {
528564 stdout.writeln ("Extracting Python distributive" );
529565 }
530566
531567 await Process .run (
532568 'tar' , ['-xzf' , pythonArchivePath, '-C' , _pythonDir! .path]);
569+
570+ if (Platform .isMacOS) {
571+ duplicateSysconfigFile (_pythonDir! .path);
572+ }
533573 }
534574 }
535575
@@ -542,6 +582,28 @@ class PackageCommand extends Command {
542582 return await runExec (pythonExePath, args, environment: environment);
543583 }
544584
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+
545607 Future <HttpServer > startSimpleServer () async {
546608 const htmlHeader = "<!DOCTYPE html><html><body>\n " ;
547609 const htmlFooter = "</body></html>\n " ;
0 commit comments