33// found in the LICENSE file.
44
55import 'package:args/args.dart' ;
6+ import 'package:meta/meta.dart' ;
67
78import '../base/common.dart' ;
9+ import '../base/deferred_component.dart' ;
810import '../base/file_system.dart' ;
911import '../base/platform.dart' ;
1012import '../cache.dart' ;
13+ import '../convert.dart' ;
1114import '../dart/pub.dart' ;
15+ import '../flutter_manifest.dart' ;
16+
1217import '../globals.dart' as globals;
1318import '../project.dart' ;
1419import '../runner/flutter_command.dart' ;
@@ -86,16 +91,17 @@ class WidgetPreviewStartCommand extends FlutterCommand
8691 @override
8792 Future <FlutterCommandResult > runCommand () async {
8893 final FlutterProject rootProject = getRootProject ();
94+ final Directory widgetPreviewScaffold = rootProject.widgetPreviewScaffold;
8995
9096 // Check to see if a preview scaffold has already been generated. If not,
9197 // generate one.
92- if (! rootProject. widgetPreviewScaffold.existsSync ()) {
98+ if (! widgetPreviewScaffold.existsSync ()) {
9399 globals.logger.printStatus (
94- 'Creating widget preview scaffolding at: ${rootProject . widgetPreviewScaffold .path }' ,
100+ 'Creating widget preview scaffolding at: ${widgetPreviewScaffold .path }' ,
95101 );
96102 await generateApp (
97103 < String > ['widget_preview_scaffold' ],
98- rootProject. widgetPreviewScaffold,
104+ widgetPreviewScaffold,
99105 createTemplateContext (
100106 organization: 'flutter' ,
101107 projectName: 'widget_preview_scaffold' ,
@@ -109,17 +115,183 @@ class WidgetPreviewStartCommand extends FlutterCommand
109115 overwrite: true ,
110116 generateMetadata: false ,
111117 );
118+ await _populatePreviewPubspec (rootProject: rootProject);
119+ }
120+ return FlutterCommandResult .success ();
121+ }
122+
123+ @visibleForTesting
124+ static const Map <String , String > flutterGenPackageConfigEntry =
125+ < String , String > {
126+ 'name' : 'flutter_gen' ,
127+ 'rootUri' : '../../flutter_gen' ,
128+ 'languageVersion' : '2.12' ,
129+ };
112130
113- if (shouldCallPubGet) {
114- await pub.get (
115- context: PubContext .create,
116- project: rootProject.widgetPreviewScaffoldProject,
117- offline: offline,
118- outputMode: PubOutputMode .summaryOnly,
131+ /// Maps asset URIs to relative paths for the widget preview project to
132+ /// include.
133+ @visibleForTesting
134+ static Uri transformAssetUri (Uri uri) {
135+ // Assets provided by packages always start with 'packages' and do not
136+ // require their URIs to be updated.
137+ if (uri.path.startsWith ('packages' )) {
138+ return uri;
139+ }
140+ // Otherwise, the asset is contained within the root project and needs
141+ // to be referenced from the widget preview scaffold project's pubspec.
142+ return Uri (path: '../../${uri .path }' );
143+ }
144+
145+ @visibleForTesting
146+ static AssetsEntry transformAssetsEntry (AssetsEntry asset) {
147+ return AssetsEntry (
148+ uri: transformAssetUri (asset.uri),
149+ flavors: asset.flavors,
150+ transformers: asset.transformers,
151+ );
152+ }
153+
154+ @visibleForTesting
155+ static FontAsset transformFontAsset (FontAsset asset) {
156+ return FontAsset (
157+ transformAssetUri (asset.assetUri),
158+ weight: asset.weight,
159+ style: asset.style,
160+ );
161+ }
162+
163+ @visibleForTesting
164+ static DeferredComponent transformDeferredComponent (
165+ DeferredComponent component) {
166+ return DeferredComponent (
167+ name: component.name,
168+ // TODO(bkonyi): verify these library paths are always package: paths from the parent project.
169+ libraries: component.libraries,
170+ assets: component.assets.map (transformAssetsEntry).toList (),
171+ );
172+ }
173+
174+ @visibleForTesting
175+ FlutterManifest buildPubspec ({
176+ required FlutterManifest rootManifest,
177+ required FlutterManifest widgetPreviewManifest,
178+ }) {
179+ final List <AssetsEntry > assets =
180+ rootManifest.assets.map (transformAssetsEntry).toList ();
181+
182+ final List <Font > fonts = rootManifest.fonts.map (
183+ (Font font) {
184+ return Font (
185+ font.familyName,
186+ font.fontAssets.map (transformFontAsset).toList (),
119187 );
120- }
188+ },
189+ ).toList ();
190+
191+ final List <Uri > shaders =
192+ rootManifest.shaders.map (transformAssetUri).toList ();
193+
194+ final List <Uri > models =
195+ rootManifest.models.map (transformAssetUri).toList ();
196+
197+ final List <DeferredComponent >? deferredComponents = rootManifest
198+ .deferredComponents
199+ ? .map (transformDeferredComponent)
200+ .toList ();
201+
202+ return widgetPreviewManifest.copyWith (
203+ logger: globals.logger,
204+ assets: assets,
205+ fonts: fonts,
206+ shaders: shaders,
207+ models: models,
208+ deferredComponents: deferredComponents,
209+ );
210+ }
211+
212+ Future <void > _populatePreviewPubspec ({
213+ required FlutterProject rootProject,
214+ }) async {
215+ final FlutterProject widgetPreviewScaffoldProject =
216+ rootProject.widgetPreviewScaffoldProject;
217+
218+ // Overwrite the pubspec for the preview scaffold project to include assets
219+ // from the root project.
220+ widgetPreviewScaffoldProject.replacePubspec (
221+ buildPubspec (
222+ rootManifest: rootProject.manifest,
223+ widgetPreviewManifest: widgetPreviewScaffoldProject.manifest,
224+ ),
225+ );
226+
227+ // Adds a path dependency on the parent project so previews can be
228+ // imported directly into the preview scaffold.
229+ const String pubAdd = 'add' ;
230+ await pub.interactively (
231+ < String > [
232+ pubAdd,
233+ '--directory' ,
234+ widgetPreviewScaffoldProject.directory.path,
235+ '${rootProject .manifest .appName }:{"path":${rootProject .directory .path }}' ,
236+ ],
237+ context: PubContext .pubAdd,
238+ command: pubAdd,
239+ touchesPackageConfig: true ,
240+ );
241+
242+ // Generate package_config.json.
243+ await pub.get (
244+ context: PubContext .create,
245+ project: widgetPreviewScaffoldProject,
246+ offline: offline,
247+ outputMode: PubOutputMode .summaryOnly,
248+ );
249+
250+ if (rootProject.manifest.generateSyntheticPackage) {
251+ maybeAddFlutterGenToPackageConfig (
252+ rootProject: rootProject,
253+ );
121254 }
122- return FlutterCommandResult .success ();
255+ }
256+
257+ /// Manually adds an entry for package:flutter_gen to the preview scaffold's
258+ /// package_config.json if the target project makes use of localization.
259+ ///
260+ /// The Flutter Tool does this when running a Flutter project with
261+ /// localization instead of modifying the user's pubspec.yaml to depend on it
262+ /// as a path dependency. Unfortunately, the preview scaffold still needs to
263+ /// add it directly to its package_config.json as the generated package name
264+ /// isn't actually flutter_gen, which pub doesn't really like, and using the
265+ /// actual package name will break applications which import
266+ /// package:flutter_gen.
267+ void maybeAddFlutterGenToPackageConfig ({
268+ required FlutterProject rootProject,
269+ }) {
270+ if (! rootProject.manifest.generateSyntheticPackage) {
271+ return ;
272+ }
273+ final FlutterProject widgetPreviewScaffoldProject =
274+ rootProject.widgetPreviewScaffoldProject;
275+ final File packageConfig = widgetPreviewScaffoldProject.packageConfig;
276+ final String previewPackageConfigPath = packageConfig.path;
277+ if (! packageConfig.existsSync ()) {
278+ throw StateError (
279+ "Could not find preview project's package_config.json at "
280+ '$previewPackageConfigPath ' ,
281+ );
282+ }
283+ final Map <String , Object ?> packageConfigJson = json.decode (
284+ packageConfig.readAsStringSync (),
285+ ) as Map <String , Object ?>;
286+ (packageConfigJson['packages' ] as List <dynamic >? )!
287+ .cast <Map <String , String >>()
288+ .add (flutterGenPackageConfigEntry);
289+ packageConfig.writeAsStringSync (
290+ json.encode (packageConfigJson),
291+ );
292+ globals.logger.printStatus (
293+ 'Added flutter_gen dependency to $previewPackageConfigPath ' ,
294+ );
123295 }
124296}
125297
0 commit comments