Skip to content

Expose the app's build name and number as compile-time constants#187935

Open
Ortes wants to merge 3 commits into
flutter:masterfrom
Ortes:expose-build-name-defines
Open

Expose the app's build name and number as compile-time constants#187935
Ortes wants to merge 3 commits into
flutter:masterfrom
Ortes:expose-build-name-defines

Conversation

@Ortes

@Ortes Ortes commented Jun 12, 2026

Copy link
Copy Markdown

The tool already injects FLUTTER_VERSION, FLUTTER_CHANNEL, framework/engine revisions (exposed as FlutterVersion) and FLUTTER_APP_FLAVOR (exposed as appFlavor) as dart-defines. The application's own version: from pubspec.yaml is the one missing field, even though the tool already parses it (FlutterManifest.buildName/buildNumber) and embeds it into version.json, Info.plist and build.gradle on every build.

Without it, the running version is unknowable on the web: version.json is fetched from the server at runtime, so a stale cached client reports the freshly deployed version instead of its own, and version gates / update prompts built on package_info_plus silently fail for exactly the users they exist for (fluttercommunity/plus_plugins#2675).

This PR:

  • injects FLUTTER_BUILD_NAME and FLUTTER_BUILD_NUMBER (the app's pubspec version, overridable via --build-name/--build-number) in FlutterCommand.getBuildInfo, following the FLUTTER_APP_FLAVOR pattern including its reserved-name guards;
  • exposes them as appBuildName / appBuildNumber constants in package:flutter/services.dart (new src/services/app_version.dart, same style as flavor.dart / flutter_version.dart);
  • adds tool tests (injection from pubspec, no injection without a version, environment/dart-define collision guards) and framework tests.

Like all String.fromEnvironment constants, these are tree-shaken when unreferenced, so nothing ships in the binary unless the app reads them.

package_info_plus is ready to consume these defines (fluttercommunity/plus_plugins#3874), which would make the fix transparent for the ecosystem. A companion proposal for the pure-Dart side (dart compile, servers/CLIs) is dart-lang/sdk#38855 — neither subsumes the other, since Flutter does not build through dartdev.

Fixes #187934

Pre-launch Checklist

@github-actions github-actions Bot added tool Affects the "flutter" command-line tool. See also t: labels. framework flutter/packages/flutter repository. See also f: labels. labels Jun 12, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces 'appBuildName' and 'appBuildNumber' constants to the services library, allowing applications to access their build name and build number at runtime. These values are injected into 'dartDefines' during the build process, with validation to prevent users from manually overriding them. Feedback suggests refining the validation logic when checking existing 'dartDefines' to avoid false positives on custom variables that share a prefix with the framework-defined build variables.

Comment thread packages/flutter_tools/lib/src/runner/flutter_command.dart Outdated
Ortes added a commit to Fullphysio/plus_plugins that referenced this pull request Jun 12, 2026
…allbacks

PackageInfoEnvironment now resolves the web version from, in order:
PACKAGE_INFO_PLUS_VERSION (explicit), FLUTTER_BUILD_NAME
(flutter/flutter#187935, injected by flutter_tools like
FLUTTER_APP_FLAVOR), and dart.package.version (dart-lang/sdk#38855).
Once either upstream proposal lands, apps need no configuration at all.
Ortes added a commit to Fullphysio/plus_plugins that referenced this pull request Jun 12, 2026
…allbacks

PackageInfoEnvironment now resolves the web version from, in order:
PACKAGE_INFO_PLUS_VERSION (explicit), FLUTTER_BUILD_NAME
(flutter/flutter#187935, injected by flutter_tools like
FLUTTER_APP_FLAVOR), and dart.package.version (dart-lang/sdk#38855).
Once either upstream proposal lands, apps need no configuration at all.
@Ortes Ortes force-pushed the expose-build-name-defines branch from 58b0f53 to ebcb817 Compare June 15, 2026 11:23
@bkonyi bkonyi added team-framework Owned by Framework team team-tool Owned by Flutter Tool team labels Jun 16, 2026

@bkonyi bkonyi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally LGTM with some minor comments. We'll also need someone from the framework to sign off on the changes to packages/flutter/.

Comment thread packages/flutter_tools/lib/src/runner/flutter_command.dart Outdated
Comment thread packages/flutter_tools/lib/src/runner/flutter_command.dart Outdated
@github-actions github-actions Bot removed the team-tool Owned by Flutter Tool team label Jun 18, 2026
@Ortes Ortes force-pushed the expose-build-name-defines branch 2 times, most recently from 29b3949 to 72d3f13 Compare June 25, 2026 16:14
@justinmc justinmc requested a review from loic-sharma June 30, 2026 22:25
: null,
);
});
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add an integration test that verifies that appBuildName and appBuildNumber have the expected values if set by the project?

See this integration test that verifies the appFlavor:

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@Tags(<String>['flutter-test-driver'])
library;
import 'package:flutter_tools/src/base/file_system.dart';
import '../src/common.dart';
import 'test_data/project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
void main() {
final Project project = _DefaultFlavorProject();
late Directory tempDir;
late FlutterTestTestDriver flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync('default_flavor_test.');
await project.setUpIn(tempDir);
flutter = FlutterTestTestDriver(tempDir);
});
tearDown(() async {
tryToDelete(tempDir);
});
testWithoutContext('Reads "default-flavor" in "flutter test"', () async {
await flutter.test();
// Without an assertion, this test always passes.
final int? exitCode = await flutter.done;
expect(exitCode, 0, reason: 'flutter test failed with exit code $exitCode');
});
}
final class _DefaultFlavorProject extends Project {
@override
final main = r'''
// Irrelevant to this test.
void main() {}
''';
@override
final pubspec = r'''
name: test
environment:
sdk: ^3.7.0-0
flutter:
default-flavor: dev
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
''';
@override
final test = r'''
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('receives default-flavor with flutter test', () async {
expect(appFlavor, 'dev');
});
}
''';
}

@loic-sharma

Copy link
Copy Markdown
Member

Hello @Ortes, apologies for the late review! This looks good to me, but let's add an integration test for the framework side (see this comment).

Also, could you rebase these changes off the latest master branch?

Once that's done, I'll kick off tests and get this landed! :)

Ortes added 3 commits June 30, 2026 19:09
The tool already injects FLUTTER_VERSION, FLUTTER_CHANNEL, framework and
engine revisions (FlutterVersion) and FLUTTER_APP_FLAVOR (appFlavor) as
dart-defines. The application's own version from pubspec.yaml is the one
missing field, even though the tool already parses it
(FlutterManifest.buildName/buildNumber) and embeds it into version.json,
Info.plist and build.gradle.

Without it, the running version is unknowable on the web: version.json
is fetched from the server at runtime, so a stale cached client reports
the freshly deployed version instead of its own. Version gates and
update prompts built on package_info_plus silently fail for exactly the
users they exist for.

This injects FLUTTER_BUILD_NAME and FLUTTER_BUILD_NUMBER (from the
--build-name/--build-number flags, falling back to the pubspec version)
following the FLUTTER_APP_FLAVOR pattern, and exposes them as
appBuildName/appBuildNumber constants in package:flutter/services.dart.
Like all String.fromEnvironment constants they are tree-shaken when
unreferenced, so nothing ships unless the app reads them.
User-provided defines that merely share a prefix with a reserved key
(e.g. FLUTTER_BUILD_NAMESPACE vs FLUTTER_BUILD_NAME) no longer trip the
reserved-key validation. Also fixes the same pre-existing prefix match
on FLUTTER_APP_FLAVOR.
@Ortes Ortes force-pushed the expose-build-name-defines branch from 72d3f13 to d3a9a37 Compare June 30, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels. team-framework Owned by Framework team tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose the app's build name/number as compile-time constants (complete the FlutterVersion/appFlavor pattern)

3 participants