flet-cli 1.0.0.dev2__py3-none-any.whl → 1.0.2__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.
- flet_cli/cli.py +66 -12
- flet_cli/commands/build.py +3 -5
- flet_cli/commands/build_base.py +145 -74
- flet_cli/commands/debug.py +2 -0
- flet_cli/commands/options.py +36 -0
- flet_cli/commands/pack.py +8 -2
- flet_cli/commands/publish.py +1 -0
- flet_cli/commands/run.py +18 -0
- flet_cli/commands/test.py +5 -3
- flet_cli/utils/android_sdk.py +26 -37
- flet_cli/utils/app_excludes.py +117 -0
- flet_cli/utils/processes.py +19 -1
- flet_cli/utils/python_versions.py +1 -1
- flet_cli/version.py +1 -1
- {flet_cli-1.0.0.dev2.dist-info → flet_cli-1.0.2.dist-info}/METADATA +7 -3
- {flet_cli-1.0.0.dev2.dist-info → flet_cli-1.0.2.dist-info}/RECORD +19 -18
- {flet_cli-1.0.0.dev2.dist-info → flet_cli-1.0.2.dist-info}/WHEEL +0 -0
- {flet_cli-1.0.0.dev2.dist-info → flet_cli-1.0.2.dist-info}/entry_points.txt +0 -0
- {flet_cli-1.0.0.dev2.dist-info → flet_cli-1.0.2.dist-info}/top_level.txt +0 -0
flet_cli/cli.py
CHANGED
|
@@ -41,6 +41,34 @@ def _render_version(as_json: bool) -> str:
|
|
|
41
41
|
return f"Flet: {flet.version.flet_version}\nFlutter: {flet.version.flutter_version}"
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
class _PositionalsFixArgumentParser(argparse.ArgumentParser):
|
|
45
|
+
"""
|
|
46
|
+
An `argparse.ArgumentParser` with the fix for CPython gh-59317 backported.
|
|
47
|
+
|
|
48
|
+
Before Python 3.12.7 and 3.13.1, `argparse` gives an optional positional its
|
|
49
|
+
default as soon as the positionals typed before an option run out, so one
|
|
50
|
+
typed after the option is rejected. `get_parser()` uses this class only on
|
|
51
|
+
those interpreters, and subparsers inherit it through `add_subparsers()`.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def _match_arguments_partial(self, actions, arg_strings_pattern):
|
|
55
|
+
"""
|
|
56
|
+
Match positionals as Python 3.12.7+ and 3.13.1+ do: when the typed
|
|
57
|
+
positionals end at an option, the trailing positionals that matched
|
|
58
|
+
nothing are left unfilled, so positionals after the option can fill them.
|
|
59
|
+
|
|
60
|
+
The matched counts add up to the length of the matched pattern, so the
|
|
61
|
+
result is unchanged on interpreters whose `argparse` already does this.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
result = super()._match_arguments_partial(actions, arg_strings_pattern)
|
|
65
|
+
matched = sum(result)
|
|
66
|
+
if matched < len(arg_strings_pattern) and arg_strings_pattern[matched] == "O":
|
|
67
|
+
while result and not result[-1]:
|
|
68
|
+
result.pop()
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
44
72
|
# Source https://stackoverflow.com/a/26379693
|
|
45
73
|
def set_default_subparser(
|
|
46
74
|
parser: argparse.ArgumentParser, name: str, args: list
|
|
@@ -88,9 +116,11 @@ def set_default_subparser(
|
|
|
88
116
|
|
|
89
117
|
def get_parser() -> argparse.ArgumentParser:
|
|
90
118
|
"""Construct and return the CLI argument parser."""
|
|
91
|
-
parser =
|
|
92
|
-
|
|
93
|
-
|
|
119
|
+
parser = (
|
|
120
|
+
_PositionalsFixArgumentParser
|
|
121
|
+
if sys.version_info < (3, 12, 7) or sys.version_info[:3] == (3, 13, 0)
|
|
122
|
+
else argparse.ArgumentParser
|
|
123
|
+
)(formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
94
124
|
|
|
95
125
|
# add version flags
|
|
96
126
|
parser.add_argument(
|
|
@@ -138,9 +168,11 @@ def split_script_args(argv: list) -> tuple[list, Optional[list]]:
|
|
|
138
168
|
"""
|
|
139
169
|
Split a command line at the first bare `--` separator.
|
|
140
170
|
|
|
141
|
-
Everything after the separator is meant for the
|
|
171
|
+
Everything after the separator is meant for the program the command runs -
|
|
172
|
+
the app script of `flet run`, pytest for `flet test`, Flutter for
|
|
173
|
+
`flet build` and `flet debug`, or PyInstaller for `flet pack` - not for Flet
|
|
142
174
|
itself, so it must be removed before the parser (and `set_default_subparser`)
|
|
143
|
-
sees it
|
|
175
|
+
sees it. Otherwise `flet run app.py -- --web` would be read as Flet's own
|
|
144
176
|
`--web` option, and `flet app.py -- build` would suppress the default `run`
|
|
145
177
|
subcommand.
|
|
146
178
|
|
|
@@ -169,10 +201,14 @@ def parse_command_line(argv: list) -> argparse.Namespace:
|
|
|
169
201
|
|
|
170
202
|
Returns:
|
|
171
203
|
The parsed arguments. For `flet run`, `script_args` holds the arguments
|
|
172
|
-
to forward to the app script
|
|
204
|
+
to forward to the app script, for `flet test`, `pytest_args` holds the
|
|
205
|
+
arguments to pass to pytest, for `flet build` and `flet debug`,
|
|
206
|
+
`flutter_build_args` holds the arguments to pass to Flutter, and for
|
|
207
|
+
`flet pack`, `pyinstaller_build_args` holds the arguments to pass to
|
|
208
|
+
PyInstaller.
|
|
173
209
|
"""
|
|
174
210
|
|
|
175
|
-
# pull off
|
|
211
|
+
# pull off arguments meant for the program the command runs before parser sees them
|
|
176
212
|
argv, script_args = split_script_args(argv)
|
|
177
213
|
|
|
178
214
|
parser = get_parser()
|
|
@@ -195,14 +231,32 @@ def parse_command_line(argv: list) -> argparse.Namespace:
|
|
|
195
231
|
)
|
|
196
232
|
parser.error(message)
|
|
197
233
|
|
|
198
|
-
# forward the arguments that followed `--` to the
|
|
234
|
+
# forward the arguments that followed `--` to the program the command runs
|
|
199
235
|
if script_args is not None:
|
|
200
|
-
|
|
236
|
+
command = getattr(args, "command", None)
|
|
237
|
+
if command == "run":
|
|
238
|
+
args.script_args = list(args.script_args) + script_args
|
|
239
|
+
elif command == "test":
|
|
240
|
+
args.pytest_args = script_args
|
|
241
|
+
elif command in ("build", "debug"):
|
|
242
|
+
if script_args:
|
|
243
|
+
args.flutter_build_args = [
|
|
244
|
+
*(args.flutter_build_args or []),
|
|
245
|
+
script_args,
|
|
246
|
+
]
|
|
247
|
+
elif command == "pack":
|
|
248
|
+
if script_args:
|
|
249
|
+
args.pyinstaller_build_args = [
|
|
250
|
+
*(args.pyinstaller_build_args or []),
|
|
251
|
+
script_args,
|
|
252
|
+
]
|
|
253
|
+
else:
|
|
201
254
|
parser.error(
|
|
202
|
-
"the `--` separator is only supported by
|
|
203
|
-
"the arguments that follow it
|
|
255
|
+
"the `--` separator is only supported by commands that run "
|
|
256
|
+
"another program, which receives the arguments that follow it: "
|
|
257
|
+
"`flet run` (the app script), `flet test` (pytest), `flet build` "
|
|
258
|
+
"and `flet debug` (Flutter), and `flet pack` (PyInstaller)"
|
|
204
259
|
)
|
|
205
|
-
args.script_args = list(args.script_args) + script_args
|
|
206
260
|
|
|
207
261
|
return args
|
|
208
262
|
|
flet_cli/commands/build.py
CHANGED
|
@@ -43,6 +43,8 @@ class Command(BaseBuildCommand):
|
|
|
43
43
|
Android (APK/AAB), and iOS (IPA and simulator .app), with a wide range of
|
|
44
44
|
customization options for metadata, assets, splash screens, and signing.
|
|
45
45
|
|
|
46
|
+
Arguments after `--` are passed down to the underlying `flutter build`.
|
|
47
|
+
|
|
46
48
|
Detailed usage guide: https://flet.dev/docs/publish
|
|
47
49
|
"""
|
|
48
50
|
|
|
@@ -206,11 +208,7 @@ class Command(BaseBuildCommand):
|
|
|
206
208
|
if build_version:
|
|
207
209
|
args.extend(["--build-name", build_version])
|
|
208
210
|
|
|
209
|
-
for arg in (
|
|
210
|
-
self.get_pyproject(f"tool.flet.{self.config_platform}.flutter.build_args")
|
|
211
|
-
or self.get_pyproject("tool.flet.flutter.build_args")
|
|
212
|
-
or []
|
|
213
|
-
):
|
|
211
|
+
for arg in self.get_platform_setting("flutter.build_args", []):
|
|
214
212
|
args.append(arg)
|
|
215
213
|
|
|
216
214
|
def run_flutter(self):
|
flet_cli/commands/build_base.py
CHANGED
|
@@ -28,6 +28,7 @@ from flet_platform_assets import (
|
|
|
28
28
|
write,
|
|
29
29
|
)
|
|
30
30
|
from packaging.requirements import Requirement
|
|
31
|
+
from rich.markup import escape
|
|
31
32
|
from rich.panel import Panel
|
|
32
33
|
from rich.table import Column, Table
|
|
33
34
|
|
|
@@ -42,10 +43,12 @@ from flet_cli.commands.flutter_base import (
|
|
|
42
43
|
verbose2_style,
|
|
43
44
|
warning_style,
|
|
44
45
|
)
|
|
46
|
+
from flet_cli.commands.options import PassThroughArgsAction
|
|
45
47
|
from flet_cli.utils.android import (
|
|
46
48
|
ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM,
|
|
47
49
|
excluded_android_abis,
|
|
48
50
|
)
|
|
51
|
+
from flet_cli.utils.app_excludes import find_default_excludes
|
|
49
52
|
from flet_cli.utils.cli import parse_cli_bool_value
|
|
50
53
|
from flet_cli.utils.hash_stamp import HashStamp
|
|
51
54
|
from flet_cli.utils.merge import merge_dict
|
|
@@ -358,6 +361,23 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
358
361
|
help="Files and/or directories to exclude from the package"
|
|
359
362
|
"; can be used multiple times",
|
|
360
363
|
)
|
|
364
|
+
parser.add_argument(
|
|
365
|
+
"--include",
|
|
366
|
+
dest="include",
|
|
367
|
+
action="extend",
|
|
368
|
+
nargs="+",
|
|
369
|
+
default=[],
|
|
370
|
+
help="Files and/or directories excluded by default to package anyway "
|
|
371
|
+
"(e.g. `.env`); can be used multiple times",
|
|
372
|
+
)
|
|
373
|
+
parser.add_argument(
|
|
374
|
+
"--no-default-excludes",
|
|
375
|
+
dest="default_excludes",
|
|
376
|
+
action="store_false",
|
|
377
|
+
default=None,
|
|
378
|
+
help="Package hidden top-level files and directories, virtual "
|
|
379
|
+
"environments and `__pycache__` directories instead of excluding them",
|
|
380
|
+
)
|
|
361
381
|
parser.add_argument(
|
|
362
382
|
"--project",
|
|
363
383
|
dest="project_name",
|
|
@@ -619,9 +639,12 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
619
639
|
parser.add_argument(
|
|
620
640
|
"--flutter-build-args",
|
|
621
641
|
dest="flutter_build_args",
|
|
622
|
-
action=
|
|
642
|
+
action=PassThroughArgsAction,
|
|
643
|
+
example="--obfuscate",
|
|
623
644
|
nargs="*",
|
|
624
|
-
help="Additional arguments for flutter build command"
|
|
645
|
+
help="Additional arguments for flutter build command. Attach an "
|
|
646
|
+
"argument that starts with `-` using `=`, e.g. "
|
|
647
|
+
"`--flutter-build-args=--obfuscate`, or pass the arguments after `--`",
|
|
625
648
|
)
|
|
626
649
|
parser.add_argument(
|
|
627
650
|
"--source-packages",
|
|
@@ -634,6 +657,7 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
634
657
|
parser.add_argument(
|
|
635
658
|
"--android-extract-packages",
|
|
636
659
|
dest="android_extract_packages",
|
|
660
|
+
action="extend",
|
|
637
661
|
nargs="+",
|
|
638
662
|
default=[],
|
|
639
663
|
help="Android only: Python packages (relative paths) to ship extracted "
|
|
@@ -860,19 +884,25 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
860
884
|
self.config_platform = self.platforms[self.target_platform]["config_platform"]
|
|
861
885
|
self.require_android_sdk = self.package_platform == "Android"
|
|
862
886
|
|
|
863
|
-
super().initialize_command()
|
|
864
|
-
|
|
865
887
|
self.python_app_path = Path(self.options.python_app_path).resolve()
|
|
866
888
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
889
|
+
# validate that the app path exists and is a directory, not a file
|
|
890
|
+
if not os.path.isdir(self.python_app_path):
|
|
891
|
+
self.skip_flutter_doctor = True
|
|
892
|
+
if os.path.isfile(self.python_app_path):
|
|
893
|
+
self.cleanup(
|
|
894
|
+
1,
|
|
895
|
+
f"Path to Flet app must be a directory, not a file: "
|
|
896
|
+
f"{escape(str(self.python_app_path))}",
|
|
897
|
+
)
|
|
870
898
|
self.cleanup(
|
|
871
899
|
1,
|
|
872
900
|
f"Path to Flet app does not exist or is not a directory: "
|
|
873
|
-
f"{self.python_app_path}",
|
|
901
|
+
f"{escape(str(self.python_app_path))}",
|
|
874
902
|
)
|
|
875
903
|
|
|
904
|
+
super().initialize_command()
|
|
905
|
+
|
|
876
906
|
self.rel_out_dir = self.options.output_dir or os.path.join(
|
|
877
907
|
"build", self.platforms[self.target_platform]["dist"]
|
|
878
908
|
)
|
|
@@ -1366,10 +1396,8 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
1366
1396
|
deep_linking_scheme = self.options.deep_linking_scheme
|
|
1367
1397
|
deep_linking_host = self.options.deep_linking_host
|
|
1368
1398
|
|
|
1369
|
-
target_arch = (
|
|
1370
|
-
|
|
1371
|
-
or self.get_pyproject(f"tool.flet.{self.config_platform}.target_arch")
|
|
1372
|
-
or self.get_pyproject("tool.flet.target_arch")
|
|
1399
|
+
target_arch = self.options.target_arch or self.get_platform_setting(
|
|
1400
|
+
"target_arch"
|
|
1373
1401
|
)
|
|
1374
1402
|
target_arch = (
|
|
1375
1403
|
target_arch
|
|
@@ -2532,11 +2560,7 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2532
2560
|
|
|
2533
2561
|
dev_packages_configured = False
|
|
2534
2562
|
if len(toml_dependencies) > 0:
|
|
2535
|
-
dev_packages = (
|
|
2536
|
-
self.get_pyproject(f"tool.flet.{self.config_platform}.dev_packages")
|
|
2537
|
-
or self.get_pyproject("tool.flet.dev_packages")
|
|
2538
|
-
or []
|
|
2539
|
-
)
|
|
2563
|
+
dev_packages = self.get_platform_setting("dev_packages", {})
|
|
2540
2564
|
if len(dev_packages) > 0:
|
|
2541
2565
|
for i in range(0, len(toml_dependencies)):
|
|
2542
2566
|
package_name = Requirement(toml_dependencies[i]).name
|
|
@@ -2616,25 +2640,63 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2616
2640
|
)
|
|
2617
2641
|
|
|
2618
2642
|
# exclude
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
app_exclude = (
|
|
2622
|
-
self.options.exclude
|
|
2623
|
-
or self.get_pyproject(f"tool.flet.{self.config_platform}.app.exclude")
|
|
2624
|
-
or self.get_pyproject("tool.flet.app.exclude")
|
|
2643
|
+
app_exclude = self.options.exclude or self.get_platform_setting(
|
|
2644
|
+
"app.exclude", []
|
|
2625
2645
|
)
|
|
2626
|
-
|
|
2627
|
-
|
|
2646
|
+
explicit_excludes = [
|
|
2647
|
+
"build",
|
|
2648
|
+
*app_exclude,
|
|
2649
|
+
*(["assets"] if self.target_platform == "web" else []),
|
|
2650
|
+
]
|
|
2628
2651
|
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2652
|
+
default_excludes: list[str] = []
|
|
2653
|
+
if self.get_bool_setting(
|
|
2654
|
+
self.options.default_excludes, "app.default_excludes", True
|
|
2655
|
+
):
|
|
2656
|
+
app_include = self.options.include or self.get_platform_setting(
|
|
2657
|
+
"app.include", []
|
|
2658
|
+
)
|
|
2659
|
+
default_excludes = find_default_excludes(
|
|
2660
|
+
self.package_app_path, app_include, explicit_excludes
|
|
2661
|
+
)
|
|
2662
|
+
|
|
2663
|
+
exclude_list = list(dict.fromkeys(explicit_excludes + default_excludes))
|
|
2664
|
+
# one flag per path: serious_python (>= 5.0.0) doesn't split values on
|
|
2665
|
+
# commas, so paths containing `,` survive
|
|
2666
|
+
for path in exclude_list:
|
|
2667
|
+
package_args.extend(["--exclude", path])
|
|
2668
|
+
|
|
2669
|
+
if default_excludes:
|
|
2670
|
+
console.log(
|
|
2671
|
+
"Excluded from app package by default: "
|
|
2672
|
+
f"{', '.join(default_excludes)} "
|
|
2673
|
+
"(use --include <path> or --no-default-excludes to package them)",
|
|
2674
|
+
markup=False,
|
|
2675
|
+
)
|
|
2676
|
+
env_files = [
|
|
2677
|
+
p for p in default_excludes if p == ".env" or p.startswith(".env.")
|
|
2678
|
+
]
|
|
2679
|
+
if env_files:
|
|
2680
|
+
it = "it" if len(env_files) == 1 else "them"
|
|
2681
|
+
console.log(
|
|
2682
|
+
f"Warning: {', '.join(env_files)} not packaged. If the app "
|
|
2683
|
+
f"loads {it} at runtime (e.g. with python-dotenv), package "
|
|
2684
|
+
f"{it} with `--include {' '.join(env_files)}` or "
|
|
2685
|
+
f"`include = {json.dumps(env_files)}` under [tool.flet.app] "
|
|
2686
|
+
"in pyproject.toml.",
|
|
2687
|
+
style=warning_style,
|
|
2688
|
+
markup=False,
|
|
2689
|
+
)
|
|
2690
|
+
if self.verbose > 0:
|
|
2691
|
+
console.log(
|
|
2692
|
+
f"App package exclude list: {exclude_list}",
|
|
2693
|
+
style=verbose1_style,
|
|
2694
|
+
markup=False,
|
|
2695
|
+
)
|
|
2632
2696
|
|
|
2633
2697
|
# source-packages
|
|
2634
|
-
source_packages = (
|
|
2635
|
-
|
|
2636
|
-
or self.get_pyproject(f"tool.flet.{self.config_platform}.source_packages")
|
|
2637
|
-
or self.get_pyproject("tool.flet.source_packages")
|
|
2698
|
+
source_packages = self.options.source_packages or self.get_platform_setting(
|
|
2699
|
+
"source_packages"
|
|
2638
2700
|
)
|
|
2639
2701
|
if source_packages:
|
|
2640
2702
|
package_env["SERIOUS_PYTHON_ALLOW_SOURCE_DISTRIBUTIONS"] = ",".join(
|
|
@@ -2651,11 +2713,7 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2651
2713
|
if self.package_platform == "Android":
|
|
2652
2714
|
user_extract_packages = (
|
|
2653
2715
|
self.options.android_extract_packages
|
|
2654
|
-
or self.
|
|
2655
|
-
f"tool.flet.{self.config_platform}.extract_packages"
|
|
2656
|
-
)
|
|
2657
|
-
or self.get_pyproject("tool.flet.extract_packages")
|
|
2658
|
-
or []
|
|
2716
|
+
or self.get_platform_setting("extract_packages", [])
|
|
2659
2717
|
)
|
|
2660
2718
|
self.android_extract_packages = list(
|
|
2661
2719
|
dict.fromkeys(ANDROID_DEFAULT_EXTRACT_PACKAGES + user_extract_packages)
|
|
@@ -2678,42 +2736,34 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2678
2736
|
|
|
2679
2737
|
if cleanup_app_files := (
|
|
2680
2738
|
self.options.cleanup_app_files
|
|
2681
|
-
or self.
|
|
2682
|
-
or self.get_pyproject("tool.flet.cleanup.app_files")
|
|
2739
|
+
or self.get_platform_setting("cleanup.app_files")
|
|
2683
2740
|
):
|
|
2684
2741
|
if isinstance(cleanup_app_files, str):
|
|
2685
2742
|
cleanup_app_files = [
|
|
2686
2743
|
value.strip() for value in cleanup_app_files.split(",")
|
|
2687
2744
|
]
|
|
2688
2745
|
if isinstance(cleanup_app_files, list):
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
)
|
|
2746
|
+
for glob_pattern in cleanup_app_files:
|
|
2747
|
+
if glob_pattern.strip():
|
|
2748
|
+
package_args.extend(
|
|
2749
|
+
["--cleanup-app-files", glob_pattern.strip()]
|
|
2750
|
+
)
|
|
2695
2751
|
cleanup_app = True
|
|
2696
2752
|
|
|
2697
2753
|
if cleanup_package_files := (
|
|
2698
2754
|
self.options.cleanup_package_files
|
|
2699
|
-
or self.
|
|
2700
|
-
f"tool.flet.{self.config_platform}.cleanup.package_files"
|
|
2701
|
-
)
|
|
2702
|
-
or self.get_pyproject("tool.flet.cleanup.package_files")
|
|
2755
|
+
or self.get_platform_setting("cleanup.package_files")
|
|
2703
2756
|
):
|
|
2704
2757
|
if isinstance(cleanup_package_files, str):
|
|
2705
2758
|
cleanup_package_files = [
|
|
2706
2759
|
value for value in cleanup_package_files.split(",")
|
|
2707
2760
|
]
|
|
2708
2761
|
if isinstance(cleanup_package_files, list):
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
),
|
|
2715
|
-
]
|
|
2716
|
-
)
|
|
2762
|
+
for glob_pattern in cleanup_package_files:
|
|
2763
|
+
if glob_pattern.strip():
|
|
2764
|
+
package_args.extend(
|
|
2765
|
+
["--cleanup-package-files", glob_pattern.strip()]
|
|
2766
|
+
)
|
|
2717
2767
|
cleanup_packages = True
|
|
2718
2768
|
|
|
2719
2769
|
if cleanup_app:
|
|
@@ -2722,6 +2772,15 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2722
2772
|
if cleanup_packages:
|
|
2723
2773
|
package_args.append("--cleanup-packages")
|
|
2724
2774
|
|
|
2775
|
+
if self.verbose > 0:
|
|
2776
|
+
console.log(
|
|
2777
|
+
f"Compile app: {'--compile-app' in package_args}, "
|
|
2778
|
+
f"cleanup app: {cleanup_app}, cleanup packages: {cleanup_packages} "
|
|
2779
|
+
"(cleanup removes serious_python's default junk globs plus any "
|
|
2780
|
+
"--cleanup-app-files/--cleanup-package-files)",
|
|
2781
|
+
style=verbose1_style,
|
|
2782
|
+
)
|
|
2783
|
+
|
|
2725
2784
|
if self.verbose > 1:
|
|
2726
2785
|
package_args.append("--verbose")
|
|
2727
2786
|
|
|
@@ -2840,24 +2899,39 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2840
2899
|
Resolved boolean-like setting value.
|
|
2841
2900
|
"""
|
|
2842
2901
|
|
|
2843
|
-
assert self.get_pyproject
|
|
2844
2902
|
return (
|
|
2845
2903
|
cli_option
|
|
2846
2904
|
if cli_option is not None
|
|
2847
|
-
else (
|
|
2848
|
-
self.get_pyproject(f"tool.flet.{self.config_platform}.{pyproj_setting}")
|
|
2849
|
-
if self.get_pyproject(
|
|
2850
|
-
f"tool.flet.{self.config_platform}.{pyproj_setting}"
|
|
2851
|
-
)
|
|
2852
|
-
is not None
|
|
2853
|
-
else (
|
|
2854
|
-
self.get_pyproject(f"tool.flet.{pyproj_setting}")
|
|
2855
|
-
if self.get_pyproject(f"tool.flet.{pyproj_setting}") is not None
|
|
2856
|
-
else default_value
|
|
2857
|
-
)
|
|
2858
|
-
)
|
|
2905
|
+
else self.get_platform_setting(pyproj_setting, default_value)
|
|
2859
2906
|
)
|
|
2860
2907
|
|
|
2908
|
+
def get_platform_setting(self, pyproj_setting, default_value=None):
|
|
2909
|
+
"""
|
|
2910
|
+
Resolve a pyproject setting with precedence: platform, global, default.
|
|
2911
|
+
|
|
2912
|
+
The platform value wins whenever it is set, even when it is empty, so
|
|
2913
|
+
`[]` or `{}` under `tool.flet.<platform>.` clears a global list or table
|
|
2914
|
+
for that platform.
|
|
2915
|
+
|
|
2916
|
+
Args:
|
|
2917
|
+
pyproj_setting: Relative key under `tool.flet.<platform>.` and
|
|
2918
|
+
`tool.flet.`.
|
|
2919
|
+
default_value: Fallback value when neither key is defined.
|
|
2920
|
+
|
|
2921
|
+
Returns:
|
|
2922
|
+
Resolved setting value.
|
|
2923
|
+
"""
|
|
2924
|
+
|
|
2925
|
+
assert self.get_pyproject
|
|
2926
|
+
for key in (
|
|
2927
|
+
f"tool.flet.{self.config_platform}.{pyproj_setting}",
|
|
2928
|
+
f"tool.flet.{pyproj_setting}",
|
|
2929
|
+
):
|
|
2930
|
+
value = self.get_pyproject(key)
|
|
2931
|
+
if value is not None:
|
|
2932
|
+
return value
|
|
2933
|
+
return default_value
|
|
2934
|
+
|
|
2861
2935
|
def add_flutter_command_args(self, args: list[str]):
|
|
2862
2936
|
"""
|
|
2863
2937
|
Hook for subclasses to append command-specific Flutter arguments.
|
|
@@ -3001,10 +3075,7 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
3001
3075
|
|
|
3002
3076
|
flutter_build_args = (
|
|
3003
3077
|
self.options.flutter_build_args
|
|
3004
|
-
or self.
|
|
3005
|
-
f"tool.flet.{self.config_platform}.flutter.build_args"
|
|
3006
|
-
)
|
|
3007
|
-
or self.get_pyproject("tool.flet.flutter.build_args")
|
|
3078
|
+
or self.get_platform_setting("flutter.build_args")
|
|
3008
3079
|
)
|
|
3009
3080
|
if flutter_build_args:
|
|
3010
3081
|
if isinstance(flutter_build_args, (list, tuple)):
|
flet_cli/commands/debug.py
CHANGED
|
@@ -13,6 +13,8 @@ class Command(BaseBuildCommand):
|
|
|
13
13
|
"""
|
|
14
14
|
Run a Flet Python app in debug mode on a specified platform (desktop, web, mobile).
|
|
15
15
|
|
|
16
|
+
Arguments after `--` are passed down to the underlying `flutter run`.
|
|
17
|
+
|
|
16
18
|
Detailed usage guide: https://flet.dev/blog/flet-debug-the-new-cli-for-testing-flet-apps-on-mobile-devices
|
|
17
19
|
"""
|
|
18
20
|
|
flet_cli/commands/options.py
CHANGED
|
@@ -32,6 +32,42 @@ class Option:
|
|
|
32
32
|
group.add_argument(*self.args, **self.kwargs)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
class PassThroughArgsAction(argparse.Action):
|
|
36
|
+
"""
|
|
37
|
+
Collect the values of each occurrence of an option that passes arguments on
|
|
38
|
+
to another program, as one list per occurrence.
|
|
39
|
+
|
|
40
|
+
A value that starts with `-` is read as an option of its own unless it is
|
|
41
|
+
attached with `=`, which leaves the occurrence without values and hands the
|
|
42
|
+
value to whichever Flet option it names. An occurrence without values is
|
|
43
|
+
therefore rejected, with a hint to attach the value or to pass it after
|
|
44
|
+
`--`.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, option_strings, dest, example: str, **kwargs):
|
|
48
|
+
"""
|
|
49
|
+
Args:
|
|
50
|
+
option_strings: The option strings of the option.
|
|
51
|
+
dest: The attribute that receives the collected values.
|
|
52
|
+
example: A value that starts with `-`, shown in the hint.
|
|
53
|
+
**kwargs: The other `add_argument()` arguments.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
super().__init__(option_strings, dest, **kwargs)
|
|
57
|
+
self.example = example
|
|
58
|
+
|
|
59
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
60
|
+
if not values:
|
|
61
|
+
raise argparse.ArgumentError(
|
|
62
|
+
self,
|
|
63
|
+
"expected at least one argument - attach one that starts with "
|
|
64
|
+
f"`-` using `=`, e.g. `{self.option_strings[0]}={self.example}`, "
|
|
65
|
+
"or pass it after `--`",
|
|
66
|
+
)
|
|
67
|
+
collected = getattr(namespace, self.dest, None) or []
|
|
68
|
+
setattr(namespace, self.dest, [*collected, values])
|
|
69
|
+
|
|
70
|
+
|
|
35
71
|
verbose_option = Option(
|
|
36
72
|
"-v",
|
|
37
73
|
"--verbose",
|
flet_cli/commands/pack.py
CHANGED
|
@@ -13,6 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
import flet_cli.__pyinstaller.config as hook_config
|
|
14
14
|
from flet.utils import is_linux, is_macos, is_windows
|
|
15
15
|
from flet_cli.commands.base import BaseCommand
|
|
16
|
+
from flet_cli.commands.options import PassThroughArgsAction
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class Command(BaseCommand):
|
|
@@ -20,6 +21,8 @@ class Command(BaseCommand):
|
|
|
20
21
|
Package a Flet application into a standalone desktop executable or app bundle
|
|
21
22
|
using PyInstaller.
|
|
22
23
|
|
|
24
|
+
Arguments after `--` are passed down to PyInstaller.
|
|
25
|
+
|
|
23
26
|
Detailed usage guide: https://flet.dev/docs/publish/using-pyinstaller
|
|
24
27
|
"""
|
|
25
28
|
|
|
@@ -152,9 +155,12 @@ class Command(BaseCommand):
|
|
|
152
155
|
parser.add_argument(
|
|
153
156
|
"--pyinstaller-build-args",
|
|
154
157
|
dest="pyinstaller_build_args",
|
|
155
|
-
action=
|
|
158
|
+
action=PassThroughArgsAction,
|
|
159
|
+
example="--clean",
|
|
156
160
|
nargs="*",
|
|
157
|
-
help="Additional raw arguments to the underlying pyinstaller build
|
|
161
|
+
help="Additional raw arguments to the underlying pyinstaller build "
|
|
162
|
+
"command. Attach an argument that starts with `-` using `=`, e.g. "
|
|
163
|
+
"`--pyinstaller-build-args=--clean`, or pass the arguments after `--`",
|
|
158
164
|
)
|
|
159
165
|
parser.add_argument(
|
|
160
166
|
"-y",
|
flet_cli/commands/publish.py
CHANGED
|
@@ -370,6 +370,7 @@ class Command(BaseCommand):
|
|
|
370
370
|
pyodide_pre=options.pre,
|
|
371
371
|
pyodide_script_path=str(script_path),
|
|
372
372
|
pyodide_version=python_release.pyodide,
|
|
373
|
+
app_package_url=app_tar_gz_filename,
|
|
373
374
|
# "canvaskit" default for the same reason as `flet build web`:
|
|
374
375
|
# "auto" puts Chromium on dart2wasm/skwasm, whose JS <-> Dart
|
|
375
376
|
# typed-data boundary costs are a large per-frame tax for
|
flet_cli/commands/run.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import argparse
|
|
2
|
+
import logging
|
|
2
3
|
import os
|
|
3
4
|
import platform
|
|
4
5
|
import signal
|
|
@@ -207,6 +208,11 @@ class Command(BaseCommand):
|
|
|
207
208
|
options: Parsed command options produced by :meth:`add_arguments`.
|
|
208
209
|
"""
|
|
209
210
|
|
|
211
|
+
if options.verbose > 0:
|
|
212
|
+
logging.basicConfig(
|
|
213
|
+
level=logging.DEBUG if options.verbose > 1 else logging.INFO
|
|
214
|
+
)
|
|
215
|
+
|
|
210
216
|
from flet.utils.pip import (
|
|
211
217
|
ensure_flet_desktop_package_installed,
|
|
212
218
|
ensure_flet_web_package_installed,
|
|
@@ -350,6 +356,7 @@ class Command(BaseCommand):
|
|
|
350
356
|
flet_app_data_dir=str(flet_app_data_dir),
|
|
351
357
|
flet_app_cache_dir=str(flet_app_cache_dir),
|
|
352
358
|
flet_app_temp_dir=str(flet_app_temp_dir),
|
|
359
|
+
verbose=options.verbose,
|
|
353
360
|
)
|
|
354
361
|
|
|
355
362
|
my_observer = Observer()
|
|
@@ -401,6 +408,7 @@ class Handler(FileSystemEventHandler):
|
|
|
401
408
|
flet_app_data_dir,
|
|
402
409
|
flet_app_cache_dir,
|
|
403
410
|
flet_app_temp_dir,
|
|
411
|
+
verbose,
|
|
404
412
|
) -> None:
|
|
405
413
|
super().__init__()
|
|
406
414
|
self.args = args
|
|
@@ -425,6 +433,7 @@ class Handler(FileSystemEventHandler):
|
|
|
425
433
|
self.flet_app_data_dir = flet_app_data_dir
|
|
426
434
|
self.flet_app_cache_dir = flet_app_cache_dir
|
|
427
435
|
self.flet_app_temp_dir = flet_app_temp_dir
|
|
436
|
+
self.verbose = verbose
|
|
428
437
|
self.terminate = threading.Event()
|
|
429
438
|
self.start_process()
|
|
430
439
|
|
|
@@ -453,7 +462,16 @@ class Handler(FileSystemEventHandler):
|
|
|
453
462
|
p_env["FLET_SERVER_UDS_PATH"] = self.uds_path
|
|
454
463
|
if self.assets_dir is not None:
|
|
455
464
|
p_env["FLET_ASSETS_DIR"] = self.assets_dir
|
|
465
|
+
# The app runs with its cwd set to the storage directory below, so a
|
|
466
|
+
# relative path the user typed in their shell has to be resolved here
|
|
467
|
+
# while that meaning still holds.
|
|
468
|
+
web_path = p_env.get("FLET_WEB_PATH")
|
|
469
|
+
if web_path and not Path(web_path).is_absolute():
|
|
470
|
+
p_env["FLET_WEB_PATH"] = str(Path(web_path).resolve())
|
|
471
|
+
|
|
456
472
|
p_env["FLET_DISPLAY_URL_PREFIX"] = self.page_url_prefix
|
|
473
|
+
if self.verbose > 0:
|
|
474
|
+
p_env["FLET_LOG_LEVEL"] = "debug" if self.verbose > 1 else "info"
|
|
457
475
|
|
|
458
476
|
p_env["FLET_APP_STORAGE_DATA"] = self.flet_app_data_dir
|
|
459
477
|
p_env["FLET_APP_STORAGE_CACHE"] = self.flet_app_cache_dir
|
flet_cli/commands/test.py
CHANGED
|
@@ -109,6 +109,7 @@ class Command(BaseBuildCommand):
|
|
|
109
109
|
`flet build`, in test mode) so the app runs on-device with embedded Python,
|
|
110
110
|
then runs pytest. Tests in the `tests/` directory drive the app through the
|
|
111
111
|
`flet_app` fixture (find controls by key, tap, take/assert screenshots).
|
|
112
|
+
Arguments after `--` are passed to pytest, e.g. `flet test -- -x`.
|
|
112
113
|
|
|
113
114
|
Detailed usage guide: https://flet.dev/docs/getting-started/integration-testing
|
|
114
115
|
"""
|
|
@@ -123,9 +124,8 @@ class Command(BaseBuildCommand):
|
|
|
123
124
|
self.flutter_test_host: Optional[str] = None
|
|
124
125
|
|
|
125
126
|
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
126
|
-
# `platform`
|
|
127
|
-
# the
|
|
128
|
-
# then our trailing `pytest_args` REMAINDER positional.
|
|
127
|
+
# register `platform` before the inherited build args, so that it precedes
|
|
128
|
+
# the `python_app_path` positional they add
|
|
129
129
|
parser.add_argument(
|
|
130
130
|
"platform",
|
|
131
131
|
type=str.lower,
|
|
@@ -170,6 +170,7 @@ class Command(BaseBuildCommand):
|
|
|
170
170
|
help="Only run tests matching the given pytest keyword expression "
|
|
171
171
|
"(passed through to pytest -k).",
|
|
172
172
|
)
|
|
173
|
+
parser.set_defaults(pytest_args=[])
|
|
173
174
|
|
|
174
175
|
def handle(self, options: argparse.Namespace) -> None:
|
|
175
176
|
super().handle(options)
|
|
@@ -191,6 +192,7 @@ class Command(BaseBuildCommand):
|
|
|
191
192
|
self.pytest_args = []
|
|
192
193
|
if options.pytest_keyword:
|
|
193
194
|
self.pytest_args += ["-k", options.pytest_keyword]
|
|
195
|
+
self.pytest_args += options.pytest_args
|
|
194
196
|
|
|
195
197
|
# Check for pytest availability before provisioning, since `flet test` runs
|
|
196
198
|
# `sys.executable -m pytest` and pytest comes from the app’s `flet[test]`
|
flet_cli/utils/android_sdk.py
CHANGED
|
@@ -13,12 +13,15 @@ ANDROID_CMDLINE_TOOLS_DOWNLOAD_VERSION = "11076708"
|
|
|
13
13
|
ANDROID_CMDLINE_TOOLS_VERSION = "12.0"
|
|
14
14
|
|
|
15
15
|
MINIMAL_PACKAGES = [
|
|
16
|
-
"cmdline-tools;latest",
|
|
17
16
|
"platform-tools",
|
|
18
17
|
"platforms;android-35",
|
|
19
18
|
"build-tools;34.0.0",
|
|
20
19
|
]
|
|
21
20
|
|
|
21
|
+
# Written to the standard input of `sdkmanager`, which reads one answer line per
|
|
22
|
+
# license prompt; `--licenses` also asks once before showing the licenses.
|
|
23
|
+
SDKMANAGER_ANSWERS = "y\n" * 50
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
class AndroidSDK:
|
|
24
27
|
"""
|
|
@@ -103,6 +106,11 @@ class AndroidSDK:
|
|
|
103
106
|
"""
|
|
104
107
|
Return the `cmdline-tools` binary directory in an SDK installation.
|
|
105
108
|
|
|
109
|
+
The version installed by this CLI is preferred over `latest`, because
|
|
110
|
+
from version 23.0, `sdkmanager` is a wrapper around the Android CLI,
|
|
111
|
+
which ignores `--licenses`, collects usage metrics by default and fails
|
|
112
|
+
on Windows.
|
|
113
|
+
|
|
106
114
|
Args:
|
|
107
115
|
home_dir: Android SDK home directory.
|
|
108
116
|
|
|
@@ -111,8 +119,8 @@ class AndroidSDK:
|
|
|
111
119
|
"""
|
|
112
120
|
|
|
113
121
|
for d in [
|
|
114
|
-
home_dir / "cmdline-tools" / "latest" / "bin",
|
|
115
122
|
home_dir / "cmdline-tools" / ANDROID_CMDLINE_TOOLS_VERSION / "bin",
|
|
123
|
+
home_dir / "cmdline-tools" / "latest" / "bin",
|
|
116
124
|
]:
|
|
117
125
|
if d.exists():
|
|
118
126
|
return d
|
|
@@ -322,36 +330,23 @@ class AndroidSDK:
|
|
|
322
330
|
RuntimeError: If package installation fails.
|
|
323
331
|
"""
|
|
324
332
|
|
|
325
|
-
|
|
333
|
+
package_dir = home_dir.joinpath(*package_name.split(";"))
|
|
334
|
+
if package_dir.exists():
|
|
326
335
|
self.log(f'Android SDK package "{package_name}" is already installed')
|
|
327
336
|
return 0
|
|
328
337
|
|
|
329
338
|
self.log(f'Installing Android SDK package "{package_name}"')
|
|
330
339
|
|
|
331
340
|
p = self.run(
|
|
332
|
-
(
|
|
333
|
-
[
|
|
334
|
-
"sh",
|
|
335
|
-
"-c",
|
|
336
|
-
f'yes | "{self.sdkmanager_exe(home_dir)}" "{package_name}"',
|
|
337
|
-
]
|
|
338
|
-
if platform.system() != "Windows"
|
|
339
|
-
else [
|
|
340
|
-
"cmd.exe",
|
|
341
|
-
"/C",
|
|
342
|
-
"echo",
|
|
343
|
-
"y",
|
|
344
|
-
"|",
|
|
345
|
-
self.sdkmanager_exe(home_dir),
|
|
346
|
-
package_name,
|
|
347
|
-
]
|
|
348
|
-
),
|
|
341
|
+
[self.sdkmanager_exe(home_dir), package_name],
|
|
349
342
|
env={"ANDROID_HOME": str(home_dir)},
|
|
350
343
|
capture_output=False,
|
|
344
|
+
input=SDKMANAGER_ANSWERS,
|
|
351
345
|
)
|
|
352
|
-
|
|
346
|
+
# sdkmanager exits with 0 when it skips a package whose license is declined
|
|
347
|
+
if p.returncode != 0 or not package_dir.exists():
|
|
353
348
|
self.log(p.stderr)
|
|
354
|
-
raise RuntimeError(
|
|
349
|
+
raise RuntimeError(f'Error installing Android SDK package "{package_name}"')
|
|
355
350
|
return 1
|
|
356
351
|
|
|
357
352
|
def _accept_licenses(self, home_dir: Path):
|
|
@@ -368,23 +363,14 @@ class AndroidSDK:
|
|
|
368
363
|
self.log("Accepting Android SDK licenses")
|
|
369
364
|
|
|
370
365
|
p = self.run(
|
|
371
|
-
(
|
|
372
|
-
["sh", "-c", f'yes | "{self.sdkmanager_exe(home_dir)}" --licenses']
|
|
373
|
-
if platform.system() != "Windows"
|
|
374
|
-
else [
|
|
375
|
-
"cmd.exe",
|
|
376
|
-
"/C",
|
|
377
|
-
"echo",
|
|
378
|
-
"y",
|
|
379
|
-
"|",
|
|
380
|
-
self.sdkmanager_exe(home_dir),
|
|
381
|
-
"--licenses",
|
|
382
|
-
]
|
|
383
|
-
),
|
|
366
|
+
[self.sdkmanager_exe(home_dir), "--licenses"],
|
|
384
367
|
env={"ANDROID_HOME": str(home_dir)},
|
|
385
368
|
capture_output=False,
|
|
369
|
+
input=SDKMANAGER_ANSWERS,
|
|
386
370
|
)
|
|
387
|
-
|
|
371
|
+
# sdkmanager exits with 0 when licenses are declined
|
|
372
|
+
license_file = home_dir / "licenses" / "android-sdk-license"
|
|
373
|
+
if p.returncode != 0 or not license_file.exists():
|
|
388
374
|
self.log(p.stderr)
|
|
389
375
|
raise RuntimeError("Error accepting Android SDK licenses")
|
|
390
376
|
|
|
@@ -415,7 +401,7 @@ class AndroidSDK:
|
|
|
415
401
|
)
|
|
416
402
|
return p.stdout
|
|
417
403
|
|
|
418
|
-
def run(self, args, env=None, cwd=None, capture_output=True):
|
|
404
|
+
def run(self, args, env=None, cwd=None, capture_output=True, input=None):
|
|
419
405
|
"""
|
|
420
406
|
Run a subprocess configured for Android SDK tooling.
|
|
421
407
|
|
|
@@ -427,6 +413,8 @@ class AndroidSDK:
|
|
|
427
413
|
env: Optional additional environment variables.
|
|
428
414
|
cwd: Optional working directory. Defaults to current directory.
|
|
429
415
|
capture_output: Forwarded to subprocess helper.
|
|
416
|
+
input: Optional text for the subprocess's standard input. Forwarded
|
|
417
|
+
to subprocess helper.
|
|
430
418
|
|
|
431
419
|
Returns:
|
|
432
420
|
Subprocess result object returned by `flet_cli.utils.processes.run`.
|
|
@@ -447,4 +435,5 @@ class AndroidSDK:
|
|
|
447
435
|
env=cmd_env,
|
|
448
436
|
capture_output=capture_output,
|
|
449
437
|
log=self.log,
|
|
438
|
+
input=input,
|
|
450
439
|
)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import stat
|
|
3
|
+
import sys
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
PYVENV_CFG = "pyvenv.cfg"
|
|
8
|
+
PYCACHE_DIR = "__pycache__"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _is_hidden(entry: os.DirEntry) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Check whether a directory entry is hidden.
|
|
14
|
+
|
|
15
|
+
An entry is hidden when its name starts with `.` (on every platform) or,
|
|
16
|
+
on Windows, when it has the hidden file attribute.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
entry: Directory entry to check.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
`True` if the entry is hidden, `False` otherwise.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
if entry.name.startswith("."):
|
|
26
|
+
return True
|
|
27
|
+
if sys.platform != "win32":
|
|
28
|
+
return False
|
|
29
|
+
try:
|
|
30
|
+
attributes = entry.stat(follow_symlinks=False).st_file_attributes
|
|
31
|
+
except OSError:
|
|
32
|
+
return False
|
|
33
|
+
return bool(attributes & stat.FILE_ATTRIBUTE_HIDDEN)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _is_venv(path: str) -> bool:
|
|
37
|
+
return os.path.isfile(os.path.join(path, PYVENV_CFG))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _normalize(paths: Iterable[str]) -> set[str]:
|
|
41
|
+
return {os.path.normpath(p.replace("/", os.sep)) for p in paths}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def find_default_excludes(
|
|
45
|
+
app_path: Path, include: Iterable[str] = (), exclude: Iterable[str] = ()
|
|
46
|
+
) -> list[str]:
|
|
47
|
+
"""
|
|
48
|
+
Find app files and directories that are excluded from the package by default.
|
|
49
|
+
|
|
50
|
+
Matched are:
|
|
51
|
+
|
|
52
|
+
- hidden files and directories directly in `app_path` (a name starting
|
|
53
|
+
with `.`, or the hidden attribute on Windows);
|
|
54
|
+
- virtual environments (directories containing `pyvenv.cfg`) at any depth;
|
|
55
|
+
- `__pycache__` directories at any depth.
|
|
56
|
+
|
|
57
|
+
Directory symlinks are followed to match the packager. Links back to an
|
|
58
|
+
ancestor are always excluded, even if included explicitly, to prevent
|
|
59
|
+
recursive copying.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
app_path: Root directory of the Python app being packaged.
|
|
63
|
+
include: Relative paths to keep even if matched by the rules above.
|
|
64
|
+
Both `/` and `\\` separators are accepted.
|
|
65
|
+
exclude: Relative paths already excluded from the package. They are
|
|
66
|
+
not scanned and not returned.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Sorted relative paths joined with `os.sep`, the form serious_python
|
|
70
|
+
compares its exclude list against.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
keep = _normalize(include)
|
|
74
|
+
skip = _normalize(exclude)
|
|
75
|
+
excludes: list[str] = []
|
|
76
|
+
|
|
77
|
+
def add(rel_path: str) -> bool:
|
|
78
|
+
if rel_path in keep:
|
|
79
|
+
return False
|
|
80
|
+
excludes.append(rel_path)
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
# top-level hidden entries
|
|
84
|
+
subdirs: list[str] = []
|
|
85
|
+
with os.scandir(app_path) as entries:
|
|
86
|
+
for entry in entries:
|
|
87
|
+
if entry.name in skip:
|
|
88
|
+
continue
|
|
89
|
+
if _is_hidden(entry) and add(entry.name):
|
|
90
|
+
continue
|
|
91
|
+
if entry.is_dir(follow_symlinks=True):
|
|
92
|
+
subdirs.append(entry.name)
|
|
93
|
+
|
|
94
|
+
# virtual environments and __pycache__ at any depth
|
|
95
|
+
for subdir in subdirs:
|
|
96
|
+
ancestors = [app_path.resolve()]
|
|
97
|
+
for dirpath, dirnames, _ in os.walk(app_path / subdir, followlinks=True):
|
|
98
|
+
rel_dir = os.path.relpath(dirpath, app_path)
|
|
99
|
+
# Keep only this path's ancestors, not a global visited set: the
|
|
100
|
+
# packager follows symlinks and copies each alias independently.
|
|
101
|
+
del ancestors[len(Path(rel_dir).parts) :]
|
|
102
|
+
resolved = Path(dirpath).resolve()
|
|
103
|
+
if resolved in ancestors:
|
|
104
|
+
# Even an included cycle must be excluded from the copy, or
|
|
105
|
+
# serious_python would recurse into it indefinitely.
|
|
106
|
+
excludes.append(rel_dir)
|
|
107
|
+
dirnames.clear()
|
|
108
|
+
continue
|
|
109
|
+
ancestors.append(resolved)
|
|
110
|
+
if (os.path.basename(dirpath) == PYCACHE_DIR or _is_venv(dirpath)) and add(
|
|
111
|
+
rel_dir
|
|
112
|
+
):
|
|
113
|
+
dirnames.clear()
|
|
114
|
+
continue
|
|
115
|
+
dirnames[:] = [d for d in dirnames if os.path.join(rel_dir, d) not in skip]
|
|
116
|
+
|
|
117
|
+
return sorted(excludes)
|
flet_cli/utils/processes.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import os
|
|
2
3
|
import subprocess
|
|
3
4
|
from typing import Optional
|
|
@@ -8,7 +9,14 @@ if is_windows():
|
|
|
8
9
|
from ctypes import windll
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
def run(
|
|
12
|
+
def run(
|
|
13
|
+
args,
|
|
14
|
+
cwd,
|
|
15
|
+
env: Optional[dict] = None,
|
|
16
|
+
capture_output=True,
|
|
17
|
+
log=None,
|
|
18
|
+
input: Optional[str] = None,
|
|
19
|
+
):
|
|
12
20
|
"""
|
|
13
21
|
Execute a subprocess command with optional streamed logging.
|
|
14
22
|
|
|
@@ -22,6 +30,9 @@ def run(args, cwd, env: Optional[dict] = None, capture_output=True, log=None):
|
|
|
22
30
|
capture_output: If `True`, run with `subprocess.run` and
|
|
23
31
|
capture output in memory. If `False`, stream combined output line by line.
|
|
24
32
|
log: Optional callback receiving each output line when `capture_output=False`.
|
|
33
|
+
input: Optional text written to the command's standard input, which is
|
|
34
|
+
then closed. When `capture_output=False`, it is written before any
|
|
35
|
+
output is read, so it must fit in the OS pipe buffer.
|
|
25
36
|
|
|
26
37
|
Returns:
|
|
27
38
|
A completed `subprocess.CompletedProcess`
|
|
@@ -49,6 +60,7 @@ def run(args, cwd, env: Optional[dict] = None, capture_output=True, log=None):
|
|
|
49
60
|
process = subprocess.run(
|
|
50
61
|
args,
|
|
51
62
|
cwd=cwd,
|
|
63
|
+
input=input,
|
|
52
64
|
capture_output=True,
|
|
53
65
|
text=True,
|
|
54
66
|
encoding="utf-8",
|
|
@@ -68,6 +80,12 @@ def run(args, cwd, env: Optional[dict] = None, capture_output=True, log=None):
|
|
|
68
80
|
errors="replace",
|
|
69
81
|
)
|
|
70
82
|
|
|
83
|
+
if input is not None:
|
|
84
|
+
# The process may exit or close its standard input before reading it all
|
|
85
|
+
with contextlib.suppress(OSError):
|
|
86
|
+
process.stdin.write(input)
|
|
87
|
+
process.stdin.close()
|
|
88
|
+
|
|
71
89
|
try:
|
|
72
90
|
while True:
|
|
73
91
|
stdout_line = process.stdout.readline()
|
|
@@ -28,7 +28,7 @@ from flet_cli.utils.template_cache import get_cache_root
|
|
|
28
28
|
# python-build release this flet pins. Keep in sync with serious_python's
|
|
29
29
|
# `pythonReleaseDate` (lib/src/python_versions.dart) — both should track the
|
|
30
30
|
# same python-build release.
|
|
31
|
-
PYTHON_BUILD_RELEASE_DATE = "
|
|
31
|
+
PYTHON_BUILD_RELEASE_DATE = "20260921"
|
|
32
32
|
|
|
33
33
|
RELEASE_DATE_ENV = "FLET_PYTHON_BUILD_RELEASE_DATE"
|
|
34
34
|
MANIFEST_PATH_ENV = "FLET_PYTHON_BUILD_MANIFEST"
|
flet_cli/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = "1.0.
|
|
1
|
+
version = "1.0.2"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flet-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Flet CLI
|
|
5
5
|
Author-email: "Appveyor Systems Inc." <hello@flet.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -9,8 +9,8 @@ Project-URL: Repository, https://github.com/flet-dev/flet
|
|
|
9
9
|
Project-URL: Documentation, https://flet.dev/docs
|
|
10
10
|
Requires-Python: >=3.10
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
Requires-Dist: flet==1.0.
|
|
13
|
-
Requires-Dist: flet-platform-assets==1.0.
|
|
12
|
+
Requires-Dist: flet==1.0.2
|
|
13
|
+
Requires-Dist: flet-platform-assets==1.0.2
|
|
14
14
|
Requires-Dist: watchdog>=4.0.0
|
|
15
15
|
Requires-Dist: packaging>=25.0
|
|
16
16
|
Requires-Dist: qrcode>=7.4.2
|
|
@@ -29,6 +29,10 @@ Requires-Dist: flet-mcp; extra == "mcp"
|
|
|
29
29
|
|
|
30
30
|
Flet CLI is a command-line interface tool for Flet, a framework for building interactive multi-platform applications using Python.
|
|
31
31
|
|
|
32
|
+
> **[Try Flet online in Flet Studio](https://studio.flet.dev)**
|
|
33
|
+
>
|
|
34
|
+
> Try an idea in your browser with no installation, then use the Flet CLI locally to develop and package your app. Write your own Python code or get help from the AI agent.
|
|
35
|
+
|
|
32
36
|
## Features
|
|
33
37
|
|
|
34
38
|
- Create new Flet projects
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
flet_cli/cli.py,sha256=
|
|
2
|
-
flet_cli/version.py,sha256=
|
|
1
|
+
flet_cli/cli.py,sha256=TlUPz73bL6rcSQBBH05uAljU9f-ZsuefEKYR-SbdPbE,10115
|
|
2
|
+
flet_cli/version.py,sha256=tTF5v0pCQ3wj3WtOg6KXgEGkOMgBeRFQACKUYV29zxo,18
|
|
3
3
|
flet_cli/__pyinstaller/__init__.py,sha256=KJsKpQ6uSVslADmFaOKaxCu-GOZZtcVKnti-ripD-ug,164
|
|
4
4
|
flet_cli/__pyinstaller/config.py,sha256=MN2IPu53K1RDyh_KE4wIcQH8El_n4Z3fD6NO2iq8y74,20
|
|
5
5
|
flet_cli/__pyinstaller/hook-flet.py,sha256=RbiFE8-7VBsY4Dwt7a9-9gW0gUFQGjOLY1-OBsZm_mI,506
|
|
@@ -9,25 +9,26 @@ flet_cli/__pyinstaller/utils.py,sha256=Im_XkCsnqactsQjKGBjUtQ9FDTPx-b-m2-8ZkEytq
|
|
|
9
9
|
flet_cli/__pyinstaller/win_utils.py,sha256=wu0G98CUMkGfloZa2LTqJE7axAZPB2-TLBOHJiYUc0Q,4428
|
|
10
10
|
flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py,sha256=O8lAxkHmNaXaZNzP-3VTuUhuglYwgJtzaeIKQa2neGU,2379
|
|
11
11
|
flet_cli/commands/base.py,sha256=6MTAuA7kKdaKIZI4AyWPM1TJfFCxmRj7kc7z2h6E_4k,3496
|
|
12
|
-
flet_cli/commands/build.py,sha256=
|
|
13
|
-
flet_cli/commands/build_base.py,sha256=
|
|
12
|
+
flet_cli/commands/build.py,sha256=mtiSHyfCbkwmbxx2308KAUgROcS8eg31acm07VhKtW4,35770
|
|
13
|
+
flet_cli/commands/build_base.py,sha256=DJvFbWOhJUsyMih3QrlmQBni6U1oiUsCydgOlOiciJE,143782
|
|
14
14
|
flet_cli/commands/clean.py,sha256=fIRr1ktoE9wzO0R-lAq985zbCH1dX6Sao5VyXiJxiOY,2250
|
|
15
15
|
flet_cli/commands/create.py,sha256=gH4BZw122BcOWlAU7MJHg8epNx9K-7lCquybacHC5pE,5859
|
|
16
|
-
flet_cli/commands/debug.py,sha256=
|
|
16
|
+
flet_cli/commands/debug.py,sha256=FNw6WGSYkuZFbz0bloyDJLKX_GxoKQ812tsOlO5W1Zk,6843
|
|
17
17
|
flet_cli/commands/devices.py,sha256=7kn56lrZLN5LuG_X3IyMdSfdFa421yE4R1otTJr5Yrc,7953
|
|
18
18
|
flet_cli/commands/doctor.py,sha256=uG2xUAT_FASB9cnLD9G83NTYdP9iuKhaHyQLjMB_E4c,1051
|
|
19
19
|
flet_cli/commands/emulators.py,sha256=2VCpu-B70JMsmqNpQoDJoXAvOV-Ex_Kkosa27D16iRk,13215
|
|
20
20
|
flet_cli/commands/flutter_base.py,sha256=1YgILbM3Qn6TSPU_GVitNJ2gQjnI_buBi8Vhd09TO6U,18285
|
|
21
21
|
flet_cli/commands/mcp.py,sha256=AVEnf6YjICqoBQPKqBaHp27KHXxFVjH-IRpAi6dSjcM,2065
|
|
22
|
-
flet_cli/commands/options.py,sha256=
|
|
23
|
-
flet_cli/commands/pack.py,sha256=
|
|
24
|
-
flet_cli/commands/publish.py,sha256=
|
|
25
|
-
flet_cli/commands/run.py,sha256=
|
|
22
|
+
flet_cli/commands/options.py,sha256=CD7Fw2CtUeuVrtM_oR1_OrAcMKF2EhRUoPuX9_3IsW8,2543
|
|
23
|
+
flet_cli/commands/pack.py,sha256=qyDLCPM0QxET21b-25Yqmtoje4bYte5XR5tLM0_Ep8Y,27045
|
|
24
|
+
flet_cli/commands/publish.py,sha256=Ra92AZ3NbBMFAnbJxBafaXvmF-3T2tXGO5V6MiHDYp8,14581
|
|
25
|
+
flet_cli/commands/run.py,sha256=7hH_6M7S6R7qkc_MNvjssI5sUUg8I8IOIEvYwOOQgA0,24248
|
|
26
26
|
flet_cli/commands/serve.py,sha256=YrollC80u9JvuXHbI_qQ3wxpjcqpIQwFxdD5nIOeOT4,3143
|
|
27
|
-
flet_cli/commands/test.py,sha256=
|
|
27
|
+
flet_cli/commands/test.py,sha256=mm9tp952BnTD9zWYcQWYyJX8DegKZgb8341OVDvDtQk,12814
|
|
28
28
|
flet_cli/commands/test_host.py,sha256=GrRLpGJf4feJsQydKZaqGX8aY1aPXPEPWCsMqEEgA2k,394
|
|
29
29
|
flet_cli/utils/android.py,sha256=aLANEliQs9B0TobjYVBYBDisr57y8Boo7ej9_KaRkx0,1635
|
|
30
|
-
flet_cli/utils/android_sdk.py,sha256=
|
|
30
|
+
flet_cli/utils/android_sdk.py,sha256=8jRccd0PArpovoNVqiAyp4me_AvQLUb8lmrSQjE0Xfc,14047
|
|
31
|
+
flet_cli/utils/app_excludes.py,sha256=A_QgScerUDKrBSXA4PL1_P9IPNjqRkBik2Y0uJ_W0UQ,3890
|
|
31
32
|
flet_cli/utils/cli.py,sha256=wPtVyfrwOdJ35acr4nVpNiS2p4SQmFD_hDsgYBBzZng,1524
|
|
32
33
|
flet_cli/utils/distros.py,sha256=q0-DOB35xY7JOf3My12sjoZUXYqNeZesBEzoc6VL0W0,3711
|
|
33
34
|
flet_cli/utils/flutter.py,sha256=lcCIAAiKKAAuoGiN304mQCUAUQZYNbL4sxcMT9KSutQ,4767
|
|
@@ -38,14 +39,14 @@ flet_cli/utils/linux_deps.py,sha256=gdT3qC6gDVdaNndkb_tkq4alyv0mhru7n6O4pdqX2wc,
|
|
|
38
39
|
flet_cli/utils/macos_sign.py,sha256=lkqHEZieroD5Nj90Cnz_dwMgaT3FJFvxaC8NV2bcP9c,53384
|
|
39
40
|
flet_cli/utils/merge.py,sha256=cZcUL1z2iaGjjFifL1KCrEj2ywLR9x6eNtOj1F2Dr6U,780
|
|
40
41
|
flet_cli/utils/plist.py,sha256=oqBCkeUjUqPIf5y7XaE2seSx64FEs7yc2cukvbjFoSU,1135
|
|
41
|
-
flet_cli/utils/processes.py,sha256=
|
|
42
|
+
flet_cli/utils/processes.py,sha256=MIX05itdoYyyE1L2N9NeTciJXk4yzjVMceONhUljwsA,3452
|
|
42
43
|
flet_cli/utils/project_dependencies.py,sha256=kC8gZZyaGk-coNXrh6cyPM6i9RhUaXcBIvZmQ50PufY,4064
|
|
43
44
|
flet_cli/utils/pyodide.py,sha256=RhumXYuovPxgLbu99pNNhVb9SRvBvhLGiUMtb9WxC5U,5058
|
|
44
45
|
flet_cli/utils/pyproject_toml.py,sha256=o4fAWKXMyK8vdnAO41BLI7Lt3ohMlVdlwrpaifq30Cs,1408
|
|
45
|
-
flet_cli/utils/python_versions.py,sha256=
|
|
46
|
+
flet_cli/utils/python_versions.py,sha256=SLMzyU5c-c8BIy3nZvvU5JqnnvGfBa2w1eBTbM9OuwQ,8129
|
|
46
47
|
flet_cli/utils/template_cache.py,sha256=Lf_iuk30zTfaNFbRmrc8s07w1yE-PYjdovZztoiuPzc,1596
|
|
47
|
-
flet_cli-1.0.
|
|
48
|
-
flet_cli-1.0.
|
|
49
|
-
flet_cli-1.0.
|
|
50
|
-
flet_cli-1.0.
|
|
51
|
-
flet_cli-1.0.
|
|
48
|
+
flet_cli-1.0.2.dist-info/METADATA,sha256=aUGuR5Xbr7datAJPO99gO_bjDlqUOj7cw-ZU7Q_ZAXg,1573
|
|
49
|
+
flet_cli-1.0.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
50
|
+
flet_cli-1.0.2.dist-info/entry_points.txt,sha256=UZFR426y3mfr0wgikEFPbZ6wtGwfgykif9Obw6R7Wnc,65
|
|
51
|
+
flet_cli-1.0.2.dist-info/top_level.txt,sha256=4_BPVAJpcNofDe3XMxajlZLBNWzWqKhRynNSBCYTKVQ,9
|
|
52
|
+
flet_cli-1.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|