Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Unreleased

- Add optional Shorebird build path:
- New config `windows_build_tool` (`flutter` default, supports `shorebird`)
- New config `shorebird_args` for `shorebird release windows` arguments
- CLI flags: `--windows-build-tool`, `--shorebird-args`

# Changelog

## 3.16.12
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ See [Configurations Examples And Use Cases].
| `architecture` | `--architecture` `-h` | Describes the architecture of the code in the package, `x64` or `arm64`, `x64` is default. | `x64` |
| `build_windows` | `--build-windows <true/false>` | If `false`, don't run the build command `flutter build windows`, default is `true`. | `true` |
| `windows_build_args` | `--windows-build-args` | Any arguments for the `flutter build windows` command. | `--obfuscate --split-debug-info=C:\Users\me\folder` |
| `windows_build_tool` | `--windows-build-tool` | Select the tool used to build Windows: `flutter` (default) or `shorebird`. | `shorebird` |
| `shorebird_args` | `--shorebird-args` | Any arguments for the `shorebird release windows` command. | `--verbose` |

</details>

Expand Down
2 changes: 1 addition & 1 deletion lib/msix.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class Msix {
}

/// Register [Logger] and [Configuration] as singleton services
_setupSingletonServices(List<String> args) {
void _setupSingletonServices(List<String> args) {
GetIt.I.registerSingleton<Logger>(args.contains('-v')
? Logger.verbose()
: Logger.standard(ansi: Ansi(true)));
Expand Down
2 changes: 1 addition & 1 deletion lib/src/assets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ class Assets {
}

/// Clear the build folder from temporary files
Future<void> cleanTemporaryFiles({clearMsixFiles = false}) async {
Future<void> cleanTemporaryFiles({bool clearMsixFiles = false}) async {
_logger.trace('cleaning temporary files');

final buildPath = _config.buildFilesFolder;
Expand Down
24 changes: 24 additions & 0 deletions lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ class Configuration {
String? executableFileName;
List<String>? signToolOptions;
List<String>? windowsBuildArgs;
// Windows build tool selection and arguments
String? windowsBuildTool; // 'flutter' (default) or 'shorebird'
List<String>? shorebirdArgs;
late Iterable<String> protocolActivation;
String? executionAlias;
String? fileExtension;
Expand Down Expand Up @@ -135,6 +138,19 @@ class Configuration {
windowsBuildArgs = commandLineConverter.convert(windowsBuildArgsConfig);
}

// windows_build_tool (defaults to 'flutter')
windowsBuildTool =
(_args['windows-build-tool'] ?? yaml['windows_build_tool'] ?? 'flutter')
?.toString();

// shorebird_args (parsed like windows_build_args)
final String? shorebirdArgsConfig =
(_args['shorebird-args'] ?? yaml['shorebird_args'])?.toString();
if (shorebirdArgsConfig != null && shorebirdArgsConfig.isNotEmpty) {
final CommandLineConverter commandLineConverter = CommandLineConverter();
shorebirdArgs = commandLineConverter.convert(shorebirdArgsConfig);
}

//CommandLineConverter
protocolActivation = _getProtocolsActivation(yaml);
fileExtension = _args['file-extension'] ?? yaml['file_extension'];
Expand Down Expand Up @@ -291,6 +307,12 @@ class Configuration {
throw 'Architecture can be "x64" or "arm64", check "msix_config: architecture" at pubspec.yaml';
}

// Validate windows_build_tool
final tool = (windowsBuildTool ?? 'flutter').toLowerCase().trim();
if (tool != 'flutter' && tool != 'shorebird') {
throw 'windows_build_tool must be either "flutter" or "shorebird". Got: "$windowsBuildTool"';
}

if (contextMenuConfiguration != null) {
if (!await File(contextMenuConfiguration!.dllPath).exists()) {
throw 'The context menu dll file not found in: ${contextMenuConfiguration!.dllPath}, check "msix_config: context_menu: dll_path" at pubspec.yaml';
Expand Down Expand Up @@ -393,6 +415,8 @@ class Configuration {
..addOption('output-name', abbr: 'n')
..addOption('signtool-options')
..addOption('windows-build-args')
..addOption('windows-build-tool')
..addOption('shorebird-args')
..addOption('protocol-activation')
..addOption('execution-alias')
..addOption('file-extension', abbr: 'f')
Expand Down
2 changes: 1 addition & 1 deletion lib/src/sign_tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ class SignTool {
return signToolOptions;
}

static isCustomSignCommand(List<String>? signToolOptions) =>
static bool isCustomSignCommand(List<String>? signToolOptions) =>
signToolOptions != null &&
signToolOptions.isNotEmpty &&
signToolOptions.containsArguments(['/sha1', '/n', '/r', '/i', '/f']);
Expand Down
72 changes: 69 additions & 3 deletions lib/src/windows_build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,21 @@ class WindowsBuild {
final Logger _logger = GetIt.I<Logger>();
final Configuration _config = GetIt.I<Configuration>();

/// Run "flutter build windows" command
/// Run the configured Windows build command
Future<void> build() async {
final tool = (_config.windowsBuildTool ?? 'flutter').toLowerCase().trim();
switch (tool) {
case 'shorebird':
await _buildWithShorebird();
return;
case 'flutter':
default:
await _buildWithFlutter();
return;
}
}

Future<void> _buildWithFlutter() async {
final flutterBuildArgs = [
'build',
'windows',
Expand All @@ -24,8 +37,10 @@ class WindowsBuild {
final Progress loggerProgress =
_logger.progress('running "flutter ${flutterBuildArgs.join(' ')}"');

_logger.trace('build windows files with the command: '
'"$flutterPath ${flutterBuildArgs.join(' ')}"');
_logger.trace(
'build windows files with the command: '
'"$flutterPath ${flutterBuildArgs.join(' ')}"',
);

ProcessResult buildProcess =
await Process.run(flutterPath, flutterBuildArgs, runInShell: true);
Expand All @@ -34,6 +49,31 @@ class WindowsBuild {

loggerProgress.finish(showTiming: true);
}

Future<void> _buildWithShorebird() async {
final shorebirdArgs = <String>[
'release',
'windows',
...?_config.shorebirdArgs,
];

var shorebirdPath = await _getShorebirdPath();

final Progress loggerProgress =
_logger.progress('running "shorebird ${shorebirdArgs.join(' ')}"');

_logger.trace(
'build windows files with the command: '
'"$shorebirdPath ${shorebirdArgs.join(' ')}"',
);

ProcessResult buildProcess =
await Process.run(shorebirdPath, shorebirdArgs, runInShell: true);

buildProcess.exitOnError();

loggerProgress.finish(showTiming: true);
}
}

Future<String> _getFlutterPath() async {
Expand All @@ -58,3 +98,29 @@ Future<String> _getFlutterPath() async {

return flutterPath;
}

Future<String> _getShorebirdPath() async {
// Respect explicit override first (if user wants a specific binary path)
final override = Platform.environment['SHOREBIRD_BIN'];
if (override != null && override.trim().isNotEmpty) {
return override.trim();
}

// default to 'shorebird' on PATH
var shorebirdPath = 'shorebird';

// Try to resolve using the Dart SDK path (similar to flutter discovery above)
final dartPath = p.split(Platform.executable);
if (dartPath.contains('dart-sdk') && dartPath.length > 4) {
final shorebirdRelativePath = p.joinAll([
...dartPath.sublist(0, dartPath.length - 4),
'shorebird',
]);

if (await File(shorebirdRelativePath).exists()) {
shorebirdPath = shorebirdRelativePath;
}
}

return shorebirdPath;
}
58 changes: 58 additions & 0 deletions test/configuration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,62 @@ msix_config:
]));
});
});

group('windows_build_tool:', () {
test('defaults to flutter when not set', () async {
await File(yamlTestPath).writeAsString(yamlContent);
await config.getConfigValues();
expect(config.windowsBuildTool?.toLowerCase(), 'flutter');
});

test('parses from YAML (shorebird)', () async {
await File(yamlTestPath)
.writeAsString('${yamlContent}windows_build_tool: shorebird');
await config.getConfigValues();
expect(config.windowsBuildTool?.toLowerCase(), 'shorebird');
});

test('parses from CLI (shorebird)', () async {
await File(yamlTestPath).writeAsString(yamlContent);
final custom = Configuration(['--windows-build-tool', 'shorebird'])
..pubspecYamlPath = yamlTestPath
..buildFilesFolder = tempFolderPath;
await custom.getConfigValues();
expect(custom.windowsBuildTool?.toLowerCase(), 'shorebird');
});

test('invalid tool is rejected in validation', () async {
await File(yamlTestPath)
.writeAsString('${yamlContent}windows_build_tool: invalidtool');
await config.getConfigValues();
await expectLater(
config.validateConfigValues,
throwsA(
predicate((String err) =>
err.toLowerCase().contains('windows_build_tool') &&
err.toLowerCase().contains('flutter') &&
err.toLowerCase().contains('shorebird')),
),
);
});
});

group('shorebird_args:', () {
test('parses from YAML string', () async {
await File(yamlTestPath).writeAsString(
'${yamlContent}shorebird_args: --verbose --flag=value');
await config.getConfigValues();
expect(config.shorebirdArgs, ['--verbose', '--flag=value']);
});

test('parses from CLI', () async {
await File(yamlTestPath).writeAsString(yamlContent);
final custom =
Configuration(['--shorebird-args', '--verbose --flag="some value"'])
..pubspecYamlPath = yamlTestPath
..buildFilesFolder = tempFolderPath;
await custom.getConfigValues();
expect(custom.shorebirdArgs, ['--verbose', '--flag=some value']);
});
});
}