flet-cli 1.1.0.dev0__py3-none-any.whl → 1.1.0.dev2__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/__pyinstaller/__init__.py +1 -1
- flet_cli/__pyinstaller/config.py +14 -0
- flet_cli/__pyinstaller/extensions.py +177 -0
- flet_cli/__pyinstaller/hook-flet_desktop.py +3 -0
- flet_cli/__pyinstaller/macos_utils.py +18 -16
- flet_cli/__pyinstaller/utils.py +3 -2
- flet_cli/__pyinstaller/win_utils.py +33 -14
- flet_cli/commands/build.py +4 -3
- flet_cli/commands/build_base.py +341 -16
- flet_cli/commands/create.py +143 -11
- flet_cli/commands/debug.py +1 -0
- flet_cli/commands/flutter_base.py +4 -5
- flet_cli/commands/pack.py +82 -3
- flet_cli/commands/test.py +1 -0
- flet_cli/utils/android.py +329 -0
- flet_cli/utils/examples.py +275 -0
- flet_cli/utils/extension_settings.py +353 -0
- flet_cli/utils/project_dependencies.py +75 -0
- flet_cli/utils/python_versions.py +1 -1
- flet_cli/version.py +1 -1
- {flet_cli-1.1.0.dev0.dist-info → flet_cli-1.1.0.dev2.dist-info}/METADATA +3 -3
- {flet_cli-1.1.0.dev0.dist-info → flet_cli-1.1.0.dev2.dist-info}/RECORD +25 -21
- {flet_cli-1.1.0.dev0.dist-info → flet_cli-1.1.0.dev2.dist-info}/WHEEL +1 -1
- {flet_cli-1.1.0.dev0.dist-info → flet_cli-1.1.0.dev2.dist-info}/entry_points.txt +0 -0
- {flet_cli-1.1.0.dev0.dist-info → flet_cli-1.1.0.dev2.dist-info}/top_level.txt +0 -0
flet_cli/commands/build_base.py
CHANGED
|
@@ -9,6 +9,7 @@ import os
|
|
|
9
9
|
import platform
|
|
10
10
|
import re
|
|
11
11
|
import shutil
|
|
12
|
+
import textwrap
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
from typing import Any, Optional, cast
|
|
14
15
|
|
|
@@ -47,10 +48,23 @@ from flet_cli.commands.flutter_base import (
|
|
|
47
48
|
from flet_cli.commands.options import PassThroughArgsAction
|
|
48
49
|
from flet_cli.utils.android import (
|
|
49
50
|
ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM,
|
|
51
|
+
ANDROID_RESOURCES_KEEP_FILE,
|
|
52
|
+
android_resource_clashes,
|
|
53
|
+
android_resource_files,
|
|
54
|
+
android_resources_keep_xml,
|
|
50
55
|
excluded_android_abis,
|
|
56
|
+
normalize_android_receivers,
|
|
57
|
+
normalize_android_services,
|
|
51
58
|
)
|
|
52
59
|
from flet_cli.utils.app_excludes import find_default_excludes
|
|
53
60
|
from flet_cli.utils.cli import parse_cli_bool_value
|
|
61
|
+
from flet_cli.utils.extension_settings import (
|
|
62
|
+
ExtensionSettings,
|
|
63
|
+
contributed_settings,
|
|
64
|
+
find_extension_settings,
|
|
65
|
+
merge_compile_options,
|
|
66
|
+
merge_gradle_dependencies,
|
|
67
|
+
)
|
|
54
68
|
from flet_cli.utils.flutter import get_flutter_dir
|
|
55
69
|
from flet_cli.utils.hash_stamp import HashStamp
|
|
56
70
|
from flet_cli.utils.log_format import annotation_path
|
|
@@ -59,6 +73,7 @@ from flet_cli.utils.plist import is_supported_plist_value, parse_cli_plist_value
|
|
|
59
73
|
from flet_cli.utils.project_dependencies import (
|
|
60
74
|
get_poetry_dependencies,
|
|
61
75
|
get_project_dependencies,
|
|
76
|
+
read_requirements_file_text,
|
|
62
77
|
)
|
|
63
78
|
from flet_cli.utils.pyproject_toml import load_pyproject_toml
|
|
64
79
|
from flet_cli.utils.python_versions import (
|
|
@@ -71,6 +86,11 @@ DEFAULT_TEMPLATE_URL = (
|
|
|
71
86
|
"v{version}/flet-build-template.zip"
|
|
72
87
|
)
|
|
73
88
|
|
|
89
|
+
DEFAULT_ANDROID_COMPILE_OPTIONS = {
|
|
90
|
+
"sourceCompatibility": "JavaVersion.VERSION_17",
|
|
91
|
+
"targetCompatibility": "JavaVersion.VERSION_17",
|
|
92
|
+
}
|
|
93
|
+
|
|
74
94
|
# Android (serious_python native-mmap packaging): pure Python ships in stored zips
|
|
75
95
|
# read via zipimport, which breaks packages that read bundled data through a real
|
|
76
96
|
# filesystem path (__file__ / pkg_resources) instead of importlib.resources. Such
|
|
@@ -150,6 +170,8 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
150
170
|
self.out_dir = None
|
|
151
171
|
self.python_module_name = None
|
|
152
172
|
self.get_pyproject = None
|
|
173
|
+
self.extensions: list[ExtensionSettings] = []
|
|
174
|
+
"""Build settings of installed extensions, read after packaging."""
|
|
153
175
|
self.python_app_path = None
|
|
154
176
|
self.build_dir = None
|
|
155
177
|
self.flutter_dir: Optional[Path] = None
|
|
@@ -713,6 +735,16 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
713
735
|
"strings, numbers, TOML arrays, or TOML inline tables "
|
|
714
736
|
"(macos only); can be used multiple times",
|
|
715
737
|
)
|
|
738
|
+
parser.add_argument(
|
|
739
|
+
"--ios-entitlements",
|
|
740
|
+
dest="ios_entitlements",
|
|
741
|
+
action="extend",
|
|
742
|
+
nargs="+",
|
|
743
|
+
default=[],
|
|
744
|
+
help="The list of `<key>=<value>` entitlements. Values can be booleans, "
|
|
745
|
+
"strings, numbers, TOML arrays, or TOML inline tables "
|
|
746
|
+
"(ipa and ios-simulator only); can be used multiple times",
|
|
747
|
+
)
|
|
716
748
|
parser.add_argument(
|
|
717
749
|
"--android-features",
|
|
718
750
|
dest="android_features",
|
|
@@ -1532,6 +1564,12 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
1532
1564
|
# Resolved before the context, because the PWA background falls
|
|
1533
1565
|
# through to it.
|
|
1534
1566
|
splash = self._resolve_splash()
|
|
1567
|
+
org_name = (
|
|
1568
|
+
self.options.org_name
|
|
1569
|
+
or self.get_pyproject(f"tool.flet.{self.config_platform}.org")
|
|
1570
|
+
or self.get_pyproject("tool.flet.org")
|
|
1571
|
+
or "com.flet"
|
|
1572
|
+
)
|
|
1535
1573
|
|
|
1536
1574
|
self.template_data = {
|
|
1537
1575
|
"out_dir": self.flutter_dir.name,
|
|
@@ -1607,12 +1645,13 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
1607
1645
|
# Pre-escaped Linux desktop entry values.
|
|
1608
1646
|
"linux_desktop_exec": self.escape_linux_desktop_exec(str(artifact_name)),
|
|
1609
1647
|
"linux_categories": linux_categories,
|
|
1610
|
-
"org_name":
|
|
1611
|
-
|
|
1612
|
-
|
|
1648
|
+
"org_name": org_name,
|
|
1649
|
+
# The template derives nothing from these: the build names files
|
|
1650
|
+
# after the bundle ID too, such as the app's Linux icons.
|
|
1613
1651
|
"bundle_id": self.options.bundle_id
|
|
1614
1652
|
or self.get_pyproject(f"tool.flet.{self.config_platform}.bundle_id")
|
|
1615
|
-
or self.get_pyproject("tool.flet.bundle_id")
|
|
1653
|
+
or self.get_pyproject("tool.flet.bundle_id")
|
|
1654
|
+
or f"{org_name}.{project_name_slug}",
|
|
1616
1655
|
"company_name": (
|
|
1617
1656
|
self.options.company_name or self.get_pyproject("tool.flet.company")
|
|
1618
1657
|
),
|
|
@@ -1623,7 +1662,10 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
1623
1662
|
"ios_signing_certificate": ios_signing_certificate,
|
|
1624
1663
|
"ios_export_options": ios_export_options,
|
|
1625
1664
|
"ios_team_id": ios_team_id,
|
|
1626
|
-
|
|
1665
|
+
# `_` makes `_options` and `_pyproject` private cookiecutter
|
|
1666
|
+
# variables, which reach the template unchanged. Cookiecutter
|
|
1667
|
+
# renders other values as Jinja and turns them into strings.
|
|
1668
|
+
"_options": {
|
|
1627
1669
|
"package_platform": self.package_platform,
|
|
1628
1670
|
"config_platform": self.config_platform,
|
|
1629
1671
|
"python_version": self.python_release.short,
|
|
@@ -1635,12 +1677,17 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
1635
1677
|
),
|
|
1636
1678
|
"info_plist": info_plist,
|
|
1637
1679
|
"macos_entitlements": macos_entitlements,
|
|
1680
|
+
"ios_entitlements": self.get_ios_entitlements(),
|
|
1638
1681
|
"android_permissions": android_permissions,
|
|
1639
1682
|
"android_features": android_features,
|
|
1640
1683
|
"android_meta_data": android_meta_data,
|
|
1641
1684
|
"android_gradle_properties": android_gradle_properties,
|
|
1685
|
+
"android_compile_options": self.get_android_compile_options(),
|
|
1686
|
+
"android_gradle_dependencies": self.get_android_gradle_dependencies(),
|
|
1642
1687
|
"android_proguard_rules": android_proguard_rules,
|
|
1643
1688
|
"android_providers": android_providers,
|
|
1689
|
+
"android_receivers": self.get_android_receivers(),
|
|
1690
|
+
"android_services": self.get_android_services(),
|
|
1644
1691
|
"deep_linking": {
|
|
1645
1692
|
"scheme": deep_linking_scheme,
|
|
1646
1693
|
"host": deep_linking_host,
|
|
@@ -1667,7 +1714,7 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
1667
1714
|
or self.icon_background_value("android")
|
|
1668
1715
|
or "#ffffff"
|
|
1669
1716
|
),
|
|
1670
|
-
"
|
|
1717
|
+
"_pyproject": self.get_pyproject(),
|
|
1671
1718
|
}
|
|
1672
1719
|
|
|
1673
1720
|
@staticmethod
|
|
@@ -2062,6 +2109,56 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2062
2109
|
f"Registered app user extensions {self.emojis['checkmark']}"
|
|
2063
2110
|
)
|
|
2064
2111
|
|
|
2112
|
+
self.apply_extension_settings()
|
|
2113
|
+
|
|
2114
|
+
def apply_extension_settings(self):
|
|
2115
|
+
"""
|
|
2116
|
+
Apply the build settings installed extensions declare in their
|
|
2117
|
+
`flet-extension.toml`, under the app's own settings.
|
|
2118
|
+
|
|
2119
|
+
Settings an extension changes are recomputed for the second template pass.
|
|
2120
|
+
"""
|
|
2121
|
+
|
|
2122
|
+
assert self.build_dir
|
|
2123
|
+
assert self.flutter_dir
|
|
2124
|
+
assert self.get_pyproject
|
|
2125
|
+
assert self.template_data
|
|
2126
|
+
|
|
2127
|
+
# serious_python installs web packages in its own default location, the
|
|
2128
|
+
# `build/site-packages` directory of the Flutter project it runs in.
|
|
2129
|
+
site_packages_dir = (
|
|
2130
|
+
self.flutter_dir / "build" / "site-packages"
|
|
2131
|
+
if self.package_platform == "Emscripten"
|
|
2132
|
+
else self.build_dir / "site-packages"
|
|
2133
|
+
)
|
|
2134
|
+
try:
|
|
2135
|
+
self.extensions, warnings = find_extension_settings(
|
|
2136
|
+
site_packages_dir, self.flutter_packages_dir
|
|
2137
|
+
)
|
|
2138
|
+
except ValueError as e:
|
|
2139
|
+
self.cleanup(1, str(e))
|
|
2140
|
+
for warning in warnings:
|
|
2141
|
+
self.warn(warning)
|
|
2142
|
+
for extension in self.extensions:
|
|
2143
|
+
contributed = contributed_settings(extension)
|
|
2144
|
+
if not contributed:
|
|
2145
|
+
continue
|
|
2146
|
+
console.log(
|
|
2147
|
+
f"Applied build settings from {extension.label}: "
|
|
2148
|
+
f"{', '.join(contributed)}",
|
|
2149
|
+
style=verbose1_style,
|
|
2150
|
+
markup=False,
|
|
2151
|
+
)
|
|
2152
|
+
|
|
2153
|
+
# Android settings only apply to Android builds, so other builds don't
|
|
2154
|
+
# fail on extensions' conflicting compile options.
|
|
2155
|
+
if self.package_platform == "Android":
|
|
2156
|
+
options = self.template_data["_options"]
|
|
2157
|
+
options["android_compile_options"] = self.get_android_compile_options()
|
|
2158
|
+
options["android_gradle_dependencies"] = (
|
|
2159
|
+
self.get_android_gradle_dependencies()
|
|
2160
|
+
)
|
|
2161
|
+
|
|
2065
2162
|
def update_flutter_dependencies(self):
|
|
2066
2163
|
"""
|
|
2067
2164
|
Merge resolved Flutter extension dependencies into `pubspec.yaml`.
|
|
@@ -2302,6 +2399,50 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2302
2399
|
return AssetSpec(ico_sizes=sizes)
|
|
2303
2400
|
return None
|
|
2304
2401
|
|
|
2402
|
+
def _site_packages_missing(self) -> bool:
|
|
2403
|
+
"""
|
|
2404
|
+
Whether any required platform package directory has no package files.
|
|
2405
|
+
|
|
2406
|
+
Check every requested mobile architecture. Empty directories and hidden
|
|
2407
|
+
staging metadata such as Darwin's `.pod` symlink do not count as packages.
|
|
2408
|
+
Darwin's architecture directories are merged into the root by packaging.
|
|
2409
|
+
|
|
2410
|
+
Returns:
|
|
2411
|
+
`True` when any required package directory is missing or empty.
|
|
2412
|
+
"""
|
|
2413
|
+
|
|
2414
|
+
assert self.flutter_dir
|
|
2415
|
+
assert self.build_dir
|
|
2416
|
+
assert self.template_data
|
|
2417
|
+
|
|
2418
|
+
if self.package_platform == "Emscripten":
|
|
2419
|
+
site_packages = self.flutter_dir / "build" / "site-packages"
|
|
2420
|
+
else:
|
|
2421
|
+
site_packages = self.build_dir / "site-packages"
|
|
2422
|
+
|
|
2423
|
+
architectures = self.template_data["_options"]["target_arch"]
|
|
2424
|
+
if self.package_platform == "Android":
|
|
2425
|
+
architectures = architectures or self.python_release.android_abis
|
|
2426
|
+
elif self.package_platform == "iOS":
|
|
2427
|
+
architectures = architectures or (
|
|
2428
|
+
"iphoneos.arm64",
|
|
2429
|
+
"iphonesimulator.arm64",
|
|
2430
|
+
"iphonesimulator.x86_64",
|
|
2431
|
+
)
|
|
2432
|
+
else:
|
|
2433
|
+
architectures = ("",)
|
|
2434
|
+
|
|
2435
|
+
def has_package_files(directory: Path) -> bool:
|
|
2436
|
+
for _, directories, files in os.walk(directory):
|
|
2437
|
+
directories[:] = [d for d in directories if not d.startswith(".")]
|
|
2438
|
+
if any(not name.startswith(".") for name in files):
|
|
2439
|
+
return True
|
|
2440
|
+
return False
|
|
2441
|
+
|
|
2442
|
+
return any(
|
|
2443
|
+
not has_package_files(site_packages / arch) for arch in architectures
|
|
2444
|
+
)
|
|
2445
|
+
|
|
2305
2446
|
def _icons_missing(self, platform: str) -> bool:
|
|
2306
2447
|
"""
|
|
2307
2448
|
Whether any icon this build should have produced is absent.
|
|
@@ -2539,12 +2680,12 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2539
2680
|
self.python_release.short,
|
|
2540
2681
|
]
|
|
2541
2682
|
|
|
2542
|
-
if self.template_data["
|
|
2683
|
+
if self.template_data["_options"]["target_arch"]:
|
|
2543
2684
|
# serious_python's --arch is a Dart multi-option: values must be
|
|
2544
2685
|
# comma-separated or the flag repeated. Space-separated values
|
|
2545
2686
|
# after the first are silently treated as positional arguments.
|
|
2546
2687
|
package_args.extend(
|
|
2547
|
-
["--arch", ",".join(self.template_data["
|
|
2688
|
+
["--arch", ",".join(self.template_data["_options"]["target_arch"])]
|
|
2548
2689
|
)
|
|
2549
2690
|
|
|
2550
2691
|
# Only the short version is passed; serious_python derives the full
|
|
@@ -2600,14 +2741,16 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2600
2741
|
package_args.extend(["-r", toml_dep])
|
|
2601
2742
|
|
|
2602
2743
|
elif requirements_txt.exists():
|
|
2744
|
+
# pip reads the file itself, so only its path is in
|
|
2745
|
+
# package_args: hash its text, and that of the files it
|
|
2746
|
+
# includes, so editing them reinstalls the packages.
|
|
2747
|
+
reqs_txt_contents = read_requirements_file_text(requirements_txt)
|
|
2603
2748
|
if self.verbose > 1:
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
)
|
|
2610
|
-
hash.update(reqs_txt_contents)
|
|
2749
|
+
console.log(
|
|
2750
|
+
f"Contents of requirements.txt: {escape(reqs_txt_contents)}",
|
|
2751
|
+
style=verbose2_style,
|
|
2752
|
+
)
|
|
2753
|
+
hash.update(reqs_txt_contents)
|
|
2611
2754
|
package_args.extend(["-r", "-r", "-r", str(requirements_txt)])
|
|
2612
2755
|
else:
|
|
2613
2756
|
package_args.extend(["-r", f"flet=={flet.version.flet_version}"])
|
|
@@ -2805,7 +2948,7 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2805
2948
|
hash.update(arg)
|
|
2806
2949
|
|
|
2807
2950
|
if not dev_packages_configured:
|
|
2808
|
-
if not hash.has_changed():
|
|
2951
|
+
if not hash.has_changed() and not self._site_packages_missing():
|
|
2809
2952
|
package_args.append("--skip-site-packages")
|
|
2810
2953
|
# serious_python skips copying Flutter packages to the temp dir
|
|
2811
2954
|
# under --skip-site-packages, so register_flutter_extensions must
|
|
@@ -2945,6 +3088,188 @@ class BaseBuildCommand(BaseFlutterCommand):
|
|
|
2945
3088
|
return value
|
|
2946
3089
|
return self.get_pyproject(f"tool.flet.deep_linking.{key}")
|
|
2947
3090
|
|
|
3091
|
+
def get_ios_entitlements(self) -> dict:
|
|
3092
|
+
"""
|
|
3093
|
+
Resolve the iOS app's entitlements.
|
|
3094
|
+
|
|
3095
|
+
Entries of `--ios-entitlements` override those of `[tool.flet.ios.entitlement]`.
|
|
3096
|
+
Values are written to `Runner.entitlements` with their plist types.
|
|
3097
|
+
"""
|
|
3098
|
+
assert self.get_pyproject
|
|
3099
|
+
assert self.options
|
|
3100
|
+
ios_entitlements = dict(self.get_pyproject("tool.flet.ios.entitlement") or {})
|
|
3101
|
+
|
|
3102
|
+
# parse --ios-entitlements
|
|
3103
|
+
for p in self.options.ios_entitlements:
|
|
3104
|
+
i = p.find("=")
|
|
3105
|
+
if i > -1:
|
|
3106
|
+
ios_entitlements[p[:i]] = parse_cli_plist_value(p[i + 1 :])
|
|
3107
|
+
else:
|
|
3108
|
+
self.cleanup(1, f"Invalid iOS entitlement option: {p}")
|
|
3109
|
+
|
|
3110
|
+
for key, value in ios_entitlements.items():
|
|
3111
|
+
if not is_supported_plist_value(value):
|
|
3112
|
+
self.cleanup(
|
|
3113
|
+
1,
|
|
3114
|
+
"Unsupported iOS entitlement value type for "
|
|
3115
|
+
f"{key}: {type(value).__name__}. Supported types are "
|
|
3116
|
+
"string, boolean, integer, float, dictionary, and arrays "
|
|
3117
|
+
"containing those values.",
|
|
3118
|
+
)
|
|
3119
|
+
return ios_entitlements
|
|
3120
|
+
|
|
3121
|
+
def get_android_compile_options(self) -> dict[str, str]:
|
|
3122
|
+
"""Merge Android compiler expressions over installed extensions' and
|
|
3123
|
+
Flet's Java 17 defaults.
|
|
3124
|
+
|
|
3125
|
+
Keys are Gradle property identifiers; values are Kotlin expressions
|
|
3126
|
+
rendered verbatim inside `android.compileOptions`.
|
|
3127
|
+
"""
|
|
3128
|
+
assert self.get_pyproject
|
|
3129
|
+
setting = "tool.flet.android.compile_options"
|
|
3130
|
+
try:
|
|
3131
|
+
configured = merge_compile_options(
|
|
3132
|
+
self.get_pyproject(setting), self.extensions
|
|
3133
|
+
)
|
|
3134
|
+
except ValueError as e:
|
|
3135
|
+
self.cleanup(1, str(e))
|
|
3136
|
+
if configured is None:
|
|
3137
|
+
configured = {}
|
|
3138
|
+
if not isinstance(configured, dict):
|
|
3139
|
+
self.cleanup(
|
|
3140
|
+
1, f"Invalid {setting}: expected a table of Kotlin expressions."
|
|
3141
|
+
)
|
|
3142
|
+
|
|
3143
|
+
resolved = DEFAULT_ANDROID_COMPILE_OPTIONS.copy()
|
|
3144
|
+
for name, expression in configured.items():
|
|
3145
|
+
if not isinstance(name, str) or not re.fullmatch(
|
|
3146
|
+
r"[A-Za-z_][A-Za-z0-9_]*", name
|
|
3147
|
+
):
|
|
3148
|
+
self.cleanup(
|
|
3149
|
+
1,
|
|
3150
|
+
f"Invalid {setting} key {name!r}: expected a property identifier.",
|
|
3151
|
+
)
|
|
3152
|
+
if not isinstance(expression, str) or not expression.strip():
|
|
3153
|
+
self.cleanup(
|
|
3154
|
+
1,
|
|
3155
|
+
f"Invalid {setting}.{name}: expected a nonempty Kotlin expression "
|
|
3156
|
+
"string.",
|
|
3157
|
+
)
|
|
3158
|
+
resolved[name] = expression.strip()
|
|
3159
|
+
return resolved
|
|
3160
|
+
|
|
3161
|
+
def get_android_gradle_dependencies(self) -> str:
|
|
3162
|
+
"""Read Kotlin declarations for the body of Gradle's dependencies block,
|
|
3163
|
+
after those of installed extensions."""
|
|
3164
|
+
assert self.get_pyproject
|
|
3165
|
+
setting = "tool.flet.android.gradle_dependencies"
|
|
3166
|
+
configured = merge_gradle_dependencies(
|
|
3167
|
+
self.get_pyproject(setting), self.extensions
|
|
3168
|
+
)
|
|
3169
|
+
if configured is None:
|
|
3170
|
+
return ""
|
|
3171
|
+
if not isinstance(configured, str):
|
|
3172
|
+
self.cleanup(
|
|
3173
|
+
1, f"Invalid {setting}: expected a string of Kotlin declarations."
|
|
3174
|
+
)
|
|
3175
|
+
return textwrap.dedent(configured).strip()
|
|
3176
|
+
|
|
3177
|
+
def get_android_receivers(self) -> dict:
|
|
3178
|
+
"""Resolve Android manifest receiver declarations from project settings."""
|
|
3179
|
+
assert self.get_pyproject
|
|
3180
|
+
try:
|
|
3181
|
+
return normalize_android_receivers(
|
|
3182
|
+
self.get_pyproject("tool.flet.android.receiver")
|
|
3183
|
+
)
|
|
3184
|
+
except ValueError as exc:
|
|
3185
|
+
self.cleanup(1, str(exc))
|
|
3186
|
+
|
|
3187
|
+
def get_android_services(self) -> dict:
|
|
3188
|
+
"""Resolve Android manifest service declarations from project settings."""
|
|
3189
|
+
assert self.get_pyproject
|
|
3190
|
+
try:
|
|
3191
|
+
return normalize_android_services(
|
|
3192
|
+
self.get_pyproject("tool.flet.android.service")
|
|
3193
|
+
)
|
|
3194
|
+
except ValueError as exc:
|
|
3195
|
+
self.cleanup(1, str(exc))
|
|
3196
|
+
|
|
3197
|
+
def copy_android_resources(self):
|
|
3198
|
+
"""
|
|
3199
|
+
Copy the app's own Android resources into the generated project.
|
|
3200
|
+
|
|
3201
|
+
`tool.flet.android.res_dir` names a directory, relative to the project,
|
|
3202
|
+
laid out like Android's `res` directory, such as `drawable/ic_stat.png`
|
|
3203
|
+
or `raw/alert.mp3`. Files copied by the previous build are removed first,
|
|
3204
|
+
so deleting a resource from the directory also removes it from the app.
|
|
3205
|
+
A keep rule protects the copied resources from release-build resource
|
|
3206
|
+
shrinking, because Python code can refer to them only by name.
|
|
3207
|
+
"""
|
|
3208
|
+
|
|
3209
|
+
assert self.get_pyproject
|
|
3210
|
+
assert self.python_app_path
|
|
3211
|
+
assert self.build_dir
|
|
3212
|
+
assert self.flutter_dir
|
|
3213
|
+
|
|
3214
|
+
if self.package_platform != "Android":
|
|
3215
|
+
return
|
|
3216
|
+
|
|
3217
|
+
res_path = self.flutter_dir / "android" / "app" / "src" / "main" / "res"
|
|
3218
|
+
manifest_path = self.build_dir / ".hash" / "android-resources"
|
|
3219
|
+
previous = (
|
|
3220
|
+
manifest_path.read_text().splitlines() if manifest_path.exists() else []
|
|
3221
|
+
)
|
|
3222
|
+
for relative in previous:
|
|
3223
|
+
(res_path / relative).unlink(missing_ok=True)
|
|
3224
|
+
|
|
3225
|
+
setting = self.get_pyproject("tool.flet.android.res_dir")
|
|
3226
|
+
if setting is None:
|
|
3227
|
+
manifest_path.unlink(missing_ok=True)
|
|
3228
|
+
return
|
|
3229
|
+
if not isinstance(setting, str):
|
|
3230
|
+
self.cleanup(
|
|
3231
|
+
1, "Invalid tool.flet.android.res_dir: expected a directory path."
|
|
3232
|
+
)
|
|
3233
|
+
res_dir = self.python_app_path / setting
|
|
3234
|
+
if not res_dir.is_dir():
|
|
3235
|
+
self.cleanup(
|
|
3236
|
+
1, f"Invalid tool.flet.android.res_dir: {res_dir} is not a directory."
|
|
3237
|
+
)
|
|
3238
|
+
try:
|
|
3239
|
+
files = android_resource_files(res_dir)
|
|
3240
|
+
except ValueError as exc:
|
|
3241
|
+
self.cleanup(1, f"Invalid tool.flet.android.res_dir entry {exc}")
|
|
3242
|
+
|
|
3243
|
+
clashes = android_resource_clashes(files, res_path)
|
|
3244
|
+
if clashes:
|
|
3245
|
+
self.cleanup(
|
|
3246
|
+
1,
|
|
3247
|
+
"Android resources in tool.flet.android.res_dir clash with "
|
|
3248
|
+
"resources Flet generates; rename them:\n"
|
|
3249
|
+
+ "\n".join(
|
|
3250
|
+
f" {file.as_posix()} (Flet's {generated.as_posix()})"
|
|
3251
|
+
for file, generated in clashes
|
|
3252
|
+
),
|
|
3253
|
+
)
|
|
3254
|
+
|
|
3255
|
+
with self.step("[bold blue]Copying Android resources..."):
|
|
3256
|
+
copied = []
|
|
3257
|
+
for relative in files:
|
|
3258
|
+
target = res_path / relative
|
|
3259
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
3260
|
+
shutil.copy2(res_dir / relative, target)
|
|
3261
|
+
copied.append(relative.as_posix())
|
|
3262
|
+
|
|
3263
|
+
keep_xml = android_resources_keep_xml(files)
|
|
3264
|
+
if keep_xml is not None:
|
|
3265
|
+
keep_path = res_path / ANDROID_RESOURCES_KEEP_FILE
|
|
3266
|
+
keep_path.parent.mkdir(parents=True, exist_ok=True)
|
|
3267
|
+
keep_path.write_text(keep_xml)
|
|
3268
|
+
copied.append(ANDROID_RESOURCES_KEEP_FILE.as_posix())
|
|
3269
|
+
|
|
3270
|
+
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
3271
|
+
manifest_path.write_text("\n".join(copied))
|
|
3272
|
+
|
|
2948
3273
|
def resolve_permissions(self) -> list[str]:
|
|
2949
3274
|
"""
|
|
2950
3275
|
The cross-platform permission bundles to apply: `--permissions`, else
|