[CP-beta]Don't throw an exception if no web define variable is set (#182548)

This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request)
Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request.

### Issue Link:
What is the link to the issue this cherry-pick is addressing?

https://github.com/flutter/flutter/issues/182243

### Impact Description:
What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)?
Does it impact development (ex. flutter doctor crashes when Android Studio is installed),
or the shipping of production apps (the app crashes on launch).
This information is for domain experts and release engineers to understand the consequences of saying yes or no to the cherry pick.

`flutter build web` is broken for apps that use the `{{...}}` pattern in `web/index.html`.

### Changelog Description:
Explain this cherry pick:
* In one line that is accessible to most Flutter developers.
* That describes the state prior to the fix.
* That includes which platforms are impacted.
See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples.

Don't throw an exception if no web define variable is set.

### Workaround:
Is there a workaround for this issue?

It's possible to workaround this issue by adding `--web-define=foo={{foo}}`.

### Risk:
What is the risk level of this cherry-pick?

### Test Coverage:
Are you confident that your fix is well-tested by automated tests?

### Validation Steps:
What are the steps to validate that this fix works?

Follow repro steps in https://github.com/flutter/flutter/issues/182243
diff --git a/packages/flutter_tools/lib/src/build_system/targets/web.dart b/packages/flutter_tools/lib/src/build_system/targets/web.dart
index 5fbf0f3..2f7ccba 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/web.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/web.dart
@@ -744,6 +744,7 @@
       serviceWorkerVersion: serviceWorkerVersion,
       flutterJsFile: flutterJsFile,
       buildConfig: buildConfig,
+      logger: environment.logger,
       webDefines: webDefines,
     );
 
@@ -768,6 +769,7 @@
           flutterJsFile: flutterJsFile,
           buildConfig: buildConfig,
           flutterBootstrapJs: bootstrapContent,
+          logger: environment.logger,
           webDefines: webDefines,
         );
         final File outputIndexHtml = fileSystem.file(
diff --git a/packages/flutter_tools/lib/src/isolated/web_asset_server.dart b/packages/flutter_tools/lib/src/isolated/web_asset_server.dart
index 6f987c1..639501b 100644
--- a/packages/flutter_tools/lib/src/isolated/web_asset_server.dart
+++ b/packages/flutter_tools/lib/src/isolated/web_asset_server.dart
@@ -79,6 +79,7 @@
     required this.webRenderer,
     required this.useLocalCanvasKit,
     required this.fileSystem,
+    required this.logger,
     Map<String, String> webDefines = const <String, String>{},
   }) : basePath = WebTemplate.baseHref(htmlTemplate(fileSystem, 'index.html', _kDefaultIndex)),
        _webDefines = webDefines {
@@ -266,6 +267,7 @@
       webRenderer: webRenderer,
       useLocalCanvasKit: useLocalCanvasKit,
       fileSystem: fileSystem,
+      logger: logger,
       webDefines: webDefines,
     );
     final int selectedPort = server.selectedPort;
@@ -595,6 +597,7 @@
   final bool useLocalCanvasKit;
 
   final FileSystem fileSystem;
+  final Logger logger;
 
   String get _buildConfigString {
     final buildConfig = <String, Object>{
@@ -634,6 +637,7 @@
       serviceWorkerVersion: null,
       buildConfig: _buildConfigString,
       flutterJsFile: _flutterJsFile,
+      logger: logger,
       webDefines: _webDefines,
     );
   }
@@ -657,6 +661,7 @@
         buildConfig: _buildConfigString,
         flutterJsFile: _flutterJsFile,
         flutterBootstrapJs: _flutterBootstrapJsContent,
+        logger: logger,
         webDefines: _webDefines,
       ),
       encoding: utf8,
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 091be82..74c50ea 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -774,7 +774,7 @@
           'Variables are replaced in the format {{VARIABLE_NAME}}.\n'
           'Multiple defines can be passed by repeating "--${FlutterOptions.kWebDefinesOption}" multiple times.\n'
           'If a template contains a variable placeholder but no corresponding "--web-define" is provided, '
-          'the build will fail with an error.',
+          'it will warn that you have an unhandled variable.',
       valueHelp: 'API_URL=https://api.example.com',
       splitCommas: false,
     );
diff --git a/packages/flutter_tools/lib/src/web_template.dart b/packages/flutter_tools/lib/src/web_template.dart
index 919f3ee..2910236 100644
--- a/packages/flutter_tools/lib/src/web_template.dart
+++ b/packages/flutter_tools/lib/src/web_template.dart
@@ -8,6 +8,7 @@
 
 import 'base/common.dart';
 import 'base/file_system.dart';
+import 'base/logger.dart';
 import 'base/utils.dart';
 
 /// Placeholder for base href
@@ -107,6 +108,7 @@
     String? buildConfig,
     String? flutterBootstrapJs,
     String? staticAssetsUrl,
+    required Logger logger,
     Map<String, String> webDefines = const <String, String>{},
   }) {
     String newContent = _content;
@@ -133,7 +135,7 @@
             "navigator.serviceWorker.register('flutter_service_worker.js?v=$serviceWorkerVersion') /* $_kServiceWorkerDeprecationNotice */",
           );
     }
-    newContent = _applyVariableSubstitutions(newContent, <String, String>{
+    newContent = _applyVariableSubstitutions(newContent, logger, <String, String>{
       ...webDefines,
       if (buildConfig != null) 'flutter_build_config': buildConfig,
       if (flutterBootstrapJs != null) 'flutter_bootstrap_js': flutterBootstrapJs,
@@ -149,8 +151,13 @@
   /// Applies web-define variable substitutions and validates all variables are provided.
   ///
   /// Replaces {{VARIABLE}} placeholders with values from webDefines. Built-in Flutter
-  /// variables are preserved if missing; user-defined variables throw ToolExit.
-  String _applyVariableSubstitutions(String content, Map<String, String> webDefines) {
+  /// variables are preserved if missing; user-defined variables will log a warning
+  /// and be skipped.
+  String _applyVariableSubstitutions(
+    String content,
+    Logger logger,
+    Map<String, String> webDefines,
+  ) {
     final variablePattern = RegExp(r'\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}');
     final missingVariables = <String>{};
 
@@ -188,13 +195,16 @@
         .map((String name) => '--web-define=$name=VALUE')
         .join(' ');
     final String variablesList = pluralize('variable', missingVariables.length);
-    throwToolExit(
-      'Missing web-define $variablesList: $variables\n\n'
-      'Please provide the missing $variablesList using:\n'
+    logger.printWarning(
+      'Warning: Missing web-define $variablesList: $variables\n\n'
+      'You can provide the missing $variablesList using:\n'
       'flutter run $suggestion\n'
       'or\n'
-      'flutter build web $suggestion',
+      'flutter build web $suggestion\n'
+      'This variable will be skipped.\n',
     );
+
+    return result;
   }
 }
 
diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart
index 3a0a118..682792b 100644
--- a/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/devfs_web_ddc_modules_test.dart
@@ -81,6 +81,7 @@
           webRenderer: WebRendererMode.canvaskit,
           useLocalCanvasKit: false,
           fileSystem: globals.fs,
+          logger: logger,
         );
         releaseAssetServer = ReleaseAssetServer(
           globals.fs.file('main.dart').uri,
@@ -329,6 +330,7 @@
       webRenderer: WebRendererMode.canvaskit,
       useLocalCanvasKit: false,
       fileSystem: globals.fs,
+      logger: logger,
     );
 
     expect(webAssetServer.basePath, 'foo/bar');
@@ -350,6 +352,7 @@
       webRenderer: WebRendererMode.canvaskit,
       useLocalCanvasKit: false,
       fileSystem: globals.fs,
+      logger: logger,
     );
 
     // Defaults to "/" when there's no base element.
@@ -373,6 +376,7 @@
         webRenderer: WebRendererMode.canvaskit,
         useLocalCanvasKit: false,
         fileSystem: globals.fs,
+        logger: logger,
       ),
       throwsToolExit(),
     );
@@ -395,6 +399,7 @@
         webRenderer: WebRendererMode.canvaskit,
         useLocalCanvasKit: false,
         fileSystem: globals.fs,
+        logger: logger,
       ),
       throwsToolExit(),
     );
@@ -1216,6 +1221,7 @@
       webRenderer: WebRendererMode.canvaskit,
       useLocalCanvasKit: false,
       fileSystem: globals.fs,
+      logger: logger,
     );
 
     expect(await webAssetServer.metadataContents('foo/main_module.ddc_merged_metadata'), null);
diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
index 6199630..195e5c2 100644
--- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart
@@ -80,6 +80,7 @@
           webRenderer: WebRendererMode.canvaskit,
           useLocalCanvasKit: false,
           fileSystem: globals.fs,
+          logger: logger,
         );
         releaseAssetServer = ReleaseAssetServer(
           globals.fs.file('main.dart').uri,
@@ -352,6 +353,7 @@
         webRenderer: WebRendererMode.canvaskit,
         useLocalCanvasKit: false,
         fileSystem: globals.fs,
+        logger: logger,
       );
 
       expect(webAssetServer.basePath, 'foo/bar');
@@ -376,6 +378,7 @@
         webRenderer: WebRendererMode.canvaskit,
         useLocalCanvasKit: false,
         fileSystem: globals.fs,
+        logger: logger,
       );
 
       // Defaults to "/" when there's no base element.
@@ -402,6 +405,7 @@
           webRenderer: WebRendererMode.canvaskit,
           useLocalCanvasKit: false,
           fileSystem: globals.fs,
+          logger: logger,
         ),
         throwsToolExit(),
       );
@@ -427,6 +431,7 @@
           webRenderer: WebRendererMode.canvaskit,
           useLocalCanvasKit: false,
           fileSystem: globals.fs,
+          logger: logger,
         ),
         throwsToolExit(),
       );
@@ -500,6 +505,7 @@
         webRenderer: WebRendererMode.canvaskit,
         useLocalCanvasKit: true,
         fileSystem: globals.fs,
+        logger: logger,
       );
 
       final Response response = await webAssetServer.handleRequest(
@@ -1530,6 +1536,7 @@
         webRenderer: WebRendererMode.canvaskit,
         useLocalCanvasKit: false,
         fileSystem: globals.fs,
+        logger: logger,
       );
 
       expect(await webAssetServer.metadataContents('foo/main_module.ddc_merged_metadata'), null);
@@ -1596,7 +1603,7 @@
   );
 
   test(
-    'WebAssetServer serves index.html with web-define variables',
+    'WebAssetServer serves index.html without user defined web-define variables',
     () => testbed.run(() async {
       // Simple test case with no custom variables - should work like before
       globals.fs.file(
@@ -1619,6 +1626,7 @@
         webRenderer: WebRendererMode.canvaskit,
         useLocalCanvasKit: false,
         fileSystem: globals.fs,
+        logger: logger,
       );
 
       final Response response = await webAssetServer.handleRequest(
@@ -1629,7 +1637,7 @@
   );
 
   test(
-    'WebAssetServer throws error for missing web-define variables in index.html',
+    'WebAssetServer warns for missing user defined web-define variables in index.html',
     () => testbed.run(() async {
       const htmlContent = '''
 <!DOCTYPE html>
@@ -1670,11 +1678,78 @@
         useLocalCanvasKit: false,
         fileSystem: globals.fs,
         webDefines: <String, String>{}, // Empty webDefines
+        logger: logger,
       );
 
+      final Response response = await webAssetServer.handleRequest(
+        Request('GET', Uri.parse('http://foobar/')),
+      );
+
+      expect(response.statusCode, HttpStatus.ok);
+      // Verify the placeholder is preserved
+      expect(await response.readAsString(), contains("const apiUrl = '{{MISSING_VAR}}';"));
+      expect(logger.warningText, contains('Missing web-define variable: MISSING_VAR'));
+    }),
+  );
+
+  test(
+    'WebAssetServer logs warning for multiple missing web-define variables in index.html',
+    () => testbed.run(() async {
+      const htmlContent = '''
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Test</title>
+  <base href="/">
+</head>
+<body>
+  <script>
+    const apiUrl = '{{MISSING_VAR_1}}';
+    const apiKey = '{{MISSING_VAR_2}}';
+  </script>
+</body>
+</html>''';
+
+      globals.fs.currentDirectory.childDirectory('web').childFile('index.html')
+        ..createSync(recursive: true)
+        ..writeAsStringSync(htmlContent);
+
+      globals.fs.file(
+          globals.fs.path.join(
+            globals.artifacts!.getHostArtifact(HostArtifact.flutterJsDirectory).path,
+            'flutter.js',
+          ),
+        )
+        ..createSync(recursive: true)
+        ..writeAsStringSync('flutter.js content');
+
+      final webAssetServer = WebAssetServer(
+        FakeHttpServer(),
+        PackageConfig.empty,
+        InternetAddress.anyIPv4,
+        <String, String>{},
+        <String, String>{},
+        usesDdcModuleSystem,
+        canaryFeatures,
+        webRenderer: WebRendererMode.canvaskit,
+        useLocalCanvasKit: false,
+        fileSystem: globals.fs,
+        webDefines: <String, String>{}, // Empty webDefines
+        logger: logger,
+      );
+
+      final Response response = await webAssetServer.handleRequest(
+        Request('GET', Uri.parse('http://foobar/')),
+      );
+
+      expect(response.statusCode, HttpStatus.ok);
+      // Verify the placeholders are preserved
+      final String responseBody = await response.readAsString();
+      expect(responseBody, contains("const apiUrl = '{{MISSING_VAR_1}}';"));
+      expect(responseBody, contains("const apiKey = '{{MISSING_VAR_2}}';"));
       expect(
-        () async => webAssetServer.handleRequest(Request('GET', Uri.parse('http://foobar/'))),
-        throwsToolExit(message: 'Missing web-define variable: MISSING_VAR'),
+        logger.warningText,
+        contains('Missing web-define variables: MISSING_VAR_1, MISSING_VAR_2'),
       );
     }),
   );
@@ -1715,6 +1790,7 @@
         useLocalCanvasKit: false,
         fileSystem: globals.fs,
         webDefines: <String, String>{'API_URL': 'https://test.api.com', 'DEBUG_MODE': 'true'},
+        logger: logger,
       );
 
       final Response response = await webAssetServer.handleRequest(
diff --git a/packages/flutter_tools/test/general.shard/web_template_test.dart b/packages/flutter_tools/test/general.shard/web_template_test.dart
index a2b0390..63b61a3 100644
--- a/packages/flutter_tools/test/general.shard/web_template_test.dart
+++ b/packages/flutter_tools/test/general.shard/web_template_test.dart
@@ -4,9 +4,12 @@
 
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
+
+import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/web_template.dart';
 
 import '../src/common.dart';
+import '../src/context.dart';
 
 const htmlSample1 = '''
 <!DOCTYPE html>
@@ -285,6 +288,7 @@
 
 void main() {
   final fs = MemoryFileSystem();
+  final logger = BufferLogger.test();
   final File flutterJs = fs.file('flutter.js');
   flutterJs.writeAsStringSync('(flutter.js content)');
 
@@ -316,6 +320,7 @@
         baseHref: '/foo/333/',
         serviceWorkerVersion: 'v123xyz',
         flutterJsFile: flutterJs,
+        logger: logger,
       ),
       htmlSample2Replaced(baseHref: '/foo/333/', serviceWorkerVersion: 'v123xyz'),
     );
@@ -328,6 +333,7 @@
         baseHref: '/foo/333/',
         serviceWorkerVersion: 'v123xyz',
         flutterJsFile: flutterJs,
+        logger: logger,
       ),
       htmlSample2Replaced(baseHref: '/foo/333/', serviceWorkerVersion: 'v123xyz'),
     );
@@ -343,6 +349,7 @@
         serviceWorkerVersion: '(service worker version)',
         flutterJsFile: flutterJs,
         buildConfig: '(build config)',
+        logger: logger,
       ),
       htmlSampleInlineFlutterJsBootstrapOutput,
     );
@@ -359,6 +366,7 @@
         flutterJsFile: flutterJs,
         buildConfig: '(build config)',
         flutterBootstrapJs: '(flutter bootstrap script)',
+        logger: logger,
       ),
       htmlSampleFullFlutterBootstrapReplacementOutput,
     );
@@ -374,6 +382,7 @@
         serviceWorkerVersion: 'v123xyz',
         flutterJsFile: flutterJs,
         staticAssetsUrl: expectedStaticAssetsUrl,
+        logger: logger,
       ),
       htmlSampleStaticAssetsUrlReplaced(staticAssetsUrl: expectedStaticAssetsUrl),
     );
@@ -387,6 +396,7 @@
       baseHref: '/foo/333/',
       serviceWorkerVersion: 'v123xyz',
       flutterJsFile: flutterJs,
+      logger: logger,
     );
     // The parsed base href should be updated after substitutions.
     expect(WebTemplate.baseHref(substituted), 'foo/333');
@@ -452,6 +462,7 @@
         'ENV': 'production',
         'DEBUG_MODE': 'false',
       },
+      logger: logger,
     );
 
     expect(result, contains("apiUrl: 'https://api.example.com'"));
@@ -459,7 +470,7 @@
     expect(result, contains('debugMode: false'));
   });
 
-  test('throws ToolExit when web-define variable is missing', () {
+  testUsingContext('logs warning when user defined web-define variable is missing', () {
     const htmlWithMissingVar = '''
 <!DOCTYPE html>
 <html>
@@ -475,18 +486,20 @@
 </html>''';
 
     const indexHtml = WebTemplate(htmlWithMissingVar);
-    expect(
-      () => indexHtml.withSubstitutions(
-        baseHref: '/',
-        serviceWorkerVersion: null,
-        flutterJsFile: flutterJs,
-        webDefines: <String, String>{}, // Missing API_URL
-      ),
-      throwsToolExit(message: 'Missing web-define variable: API_URL'),
+    final String result = indexHtml.withSubstitutions(
+      baseHref: '/',
+      serviceWorkerVersion: null,
+      flutterJsFile: flutterJs,
+      webDefines: <String, String>{}, // Missing API_URL
+      logger: testLogger,
     );
+
+    expect(testLogger.warningText, contains('Missing web-define variable: API_URL'));
+    // Verify the placeholder is preserved
+    expect(result, contains("const apiUrl = '{{API_URL}}';"));
   });
 
-  test('throws ToolExit with multiple missing variables', () {
+  testUsingContext('logs warning with multiple missing user defined variables', () {
     const htmlWithMultipleMissingVars = '''
 <!DOCTYPE html>
 <html>
@@ -506,19 +519,23 @@
 </html>''';
 
     const indexHtml = WebTemplate(htmlWithMultipleMissingVars);
-    expect(
-      () => indexHtml.withSubstitutions(
-        baseHref: '/',
-        serviceWorkerVersion: null,
-        flutterJsFile: flutterJs,
-        webDefines: <String, String>{'API_URL': 'test'}, // Missing ENV, VERSION
-      ),
-      throwsToolExit(message: 'Missing web-define variable'),
+    final String result = indexHtml.withSubstitutions(
+      baseHref: '/',
+      serviceWorkerVersion: null,
+      flutterJsFile: flutterJs,
+      webDefines: <String, String>{'API_URL': 'test'}, // Missing ENV, VERSION
+      logger: testLogger,
     );
+
+    expect(testLogger.warningText, contains('Missing web-define variables: ENV, VERSION'));
+    expect(result, contains("env: '{{ENV}}'"));
+    expect(result, contains("version: '{{VERSION}}'"));
   });
 
-  test('ignores Flutter built-in variables when validating web-define variables', () {
-    const htmlWithBuiltInVars = '''
+  testUsingContext(
+    'ignores Flutter built-in variables and logs warning for missing user variables',
+    () {
+      const htmlWithBuiltInVars = '''
 <!DOCTYPE html>
 <html>
 <head>
@@ -534,18 +551,20 @@
 </body>
 </html>''';
 
-    const indexHtml = WebTemplate(htmlWithBuiltInVars);
-    expect(
-      () => indexHtml.withSubstitutions(
+      const indexHtml = WebTemplate(htmlWithBuiltInVars);
+      final String result = indexHtml.withSubstitutions(
         baseHref: '/',
         serviceWorkerVersion: null,
         flutterJsFile: flutterJs,
         buildConfig: 'test config',
         webDefines: <String, String>{}, // Missing CUSTOM_VAR but built-in vars should be ignored
-      ),
-      throwsToolExit(message: 'Missing web-define variable: CUSTOM_VAR'),
-    );
-  });
+        logger: testLogger,
+      );
+
+      expect(testLogger.warningText, contains('Missing web-define variable: CUSTOM_VAR'));
+      expect(result, contains("const customVar = '{{CUSTOM_VAR}}';"));
+    },
+  );
 
   test('allows empty web-define variables', () {
     const htmlWithEmptyVar = '''
@@ -568,6 +587,7 @@
       serviceWorkerVersion: null,
       flutterJsFile: flutterJs,
       webDefines: <String, String>{'EMPTY_VAR': ''},
+      logger: logger,
     );
 
     expect(result, contains("const value = '';"));