| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:io'; |
| |
| import 'package:code_assets/code_assets.dart'; |
| import 'package:dartdev/src/commands/compile.dart' |
| show CompileNativeCommand, crossCompileErrorExitCode; |
| import 'package:hooks_runner/hooks_runner.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:test/test.dart'; |
| |
| import '../utils.dart'; |
| import 'compile_test.dart' show targetingHostOSMessage, usingTargetOSMessage; |
| |
| void main() { |
| ensureRunFromSdkBinDart(); |
| |
| // Cross compilation is not available on 32-bit architectures. |
| final hostArch = Target.current.architecture; |
| final bool isRunningOn32Bit = hostArch == Architecture.ia32 || |
| hostArch == Architecture.arm || |
| hostArch == Architecture.riscv32; |
| |
| group('cross compile -', defineCrossCompileTests, |
| timeout: longTimeout, skip: isRunningOn32Bit); |
| } |
| |
| String unsupportedTargetMessage(Target target) => |
| 'Unsupported target platform $target'; |
| typedef TestFunction = Future<void> Function(); |
| |
| void defineCrossCompileTests() { |
| final subcommands = [ |
| CompileNativeCommand.aotSnapshotCmdName, |
| CompileNativeCommand.exeCmdName, |
| ]; |
| final crossCompileTargets = CompileNativeCommand.supportedTargetPlatforms; |
| String mainMessage(Target target) => 'I love ${target.os}'; |
| |
| Future<(ProcessResult, String)> crossCompile( |
| String subcommand, Target target) async { |
| final p = |
| project(mainSrc: 'void main() {print("${mainMessage(target)}");}'); |
| final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath)); |
| final filename = subcommand == CompileNativeCommand.exeCmdName |
| ? 'myexe' |
| : subcommand == CompileNativeCommand.aotSnapshotCmdName |
| ? 'out.so' |
| : throw ArgumentError( |
| 'Unexpected subcommand $subcommand', 'subcommand'); |
| final outFile = path.canonicalize(path.join(p.dirPath, filename)); |
| final result = await p.run( |
| [ |
| 'compile', |
| subcommand, |
| '-v', |
| '--target-os', |
| target.os.name, |
| '--target-arch', |
| target.architecture.name, |
| '-o', |
| outFile, |
| inFile, |
| ], |
| ); |
| |
| print('Subcommand terminated with exit code ${result.exitCode}.'); |
| if (result.stdout.isNotEmpty) { |
| print('Subcommand stdout:'); |
| print(result.stdout); |
| } |
| if (result.stderr.isNotEmpty) { |
| print('Subcommand stderr:'); |
| print(result.stderr); |
| } |
| |
| return (result, outFile); |
| } |
| |
| TestFunction crossCompileTest(String subcommand, Target target) => () async { |
| expect(subcommand, isIn(subcommands)); |
| expect(target, isIn(crossCompileTargets)); |
| var (result, outFile) = await crossCompile(subcommand, target); |
| |
| expect(result.stdout, contains(usingTargetOSMessage(target.os))); |
| expect( |
| result.stderr, isNot(contains(unsupportedTargetMessage(target)))); |
| expect(result.exitCode, 0); |
| expect(File(outFile).existsSync(), true, |
| reason: 'File not found: $outFile'); |
| |
| if (target != Target.current) return; |
| |
| if (subcommand == CompileNativeCommand.exeCmdName) { |
| result = Process.runSync(outFile, const []); |
| } else { |
| expect(subcommand, CompileNativeCommand.aotSnapshotCmdName); |
| final Directory binDir = File(Platform.resolvedExecutable).parent; |
| result = Process.runSync( |
| path.join(binDir.path, 'dartaotruntime'), |
| [outFile], |
| ); |
| } |
| |
| expect(result.stdout, contains(mainMessage(target))); |
| expect(result.stderr, isEmpty); |
| expect(result.exitCode, 0); |
| }; |
| |
| TestFunction crossCompileFailureTest(String subcommand, Target target) => |
| () async { |
| expect(subcommand, isIn(subcommands)); |
| expect(target, isNot(Target.current)); |
| expect(target, isNot(isIn(crossCompileTargets))); |
| final (result, outFile) = await crossCompile(subcommand, target); |
| |
| expect(result.stdout, isNot(contains(targetingHostOSMessage))); |
| expect(result.stdout, isNot(contains(usingTargetOSMessage(target.os)))); |
| expect(result.stderr, contains(unsupportedTargetMessage(target))); |
| expect(result.exitCode, crossCompileErrorExitCode); |
| expect(File(outFile).existsSync(), false, |
| reason: 'File created despite failure: $outFile'); |
| }; |
| |
| for (final subcommand in subcommands) { |
| for (final target in crossCompileTargets) { |
| test('Compile $subcommand can cross compile to $target', |
| crossCompileTest(subcommand, target)); |
| } |
| var targetOS = Platform.isWindows ? OS.macOS : OS.windows; |
| var targetArch = Architecture.arm64; |
| var target = Target.fromArchitectureAndOS(targetArch, targetOS); |
| test('Compile $subcommand fails on invalid target OS', |
| crossCompileFailureTest(subcommand, target)); |
| |
| targetOS = OS.linux; |
| targetArch = Architecture.riscv32; |
| target = Target.fromArchitectureAndOS(targetArch, targetOS); |
| test('Compile $subcommand fails on invalid target architecture', |
| crossCompileFailureTest(subcommand, target)); |
| } |
| } |