blob: fd8514cbf3aed881ae1671de312e5bfd1b85a476 [file]
// Copyright (c) 2026, 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.
// Regression test for the MSVC archiver invocation. `RunCBuilder.runCl` must
// pass the archiver (`lib.exe`) the object file names derived from the sources
// (e.g. `add.obj`), not `*.obj`.
//
// `cl.exe /c` writes one object file per source into the build output
// directory, and `lib.exe` expands `*.obj` against that directory. So with
// `*.obj` the archiver sweeps in *any* `.obj` file left in the output
// directory by another build, corrupting the archive (and failing outright if
// such a leftover object is not a valid COFF file). Passing the derived names
// keeps the archive contents independent of unrelated files in the directory.
//
// This guards against a well-meaning simplification back to `*.obj`.
@TestOn('windows')
@OnPlatform({'windows': Timeout.factor(10)})
library;
import 'dart:io';
import 'package:code_assets/code_assets.dart';
import 'package:hooks/hooks.dart';
import 'package:native_toolchain_c/native_toolchain_c.dart';
import 'package:test/test.dart';
import '../helpers.dart';
void main() {
if (!Platform.isWindows) {
// Avoid needing status files on Dart SDK CI.
return;
}
test('CBuilder static library ignores stray .obj files in the output '
'directory', () async {
final tempUri = await tempDirForTest();
final tempUri2 = await tempDirForTest();
final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c');
const name = 'add';
final buildInputBuilder = BuildInputBuilder()
..setupShared(
packageName: name,
packageRoot: tempUri,
outputFile: tempUri.resolve('output.json'),
outputDirectoryShared: tempUri2,
)
..config.setupBuild(linkingEnabled: false)
..addExtension(
CodeAssetExtension(
targetOS: OS.windows,
targetArchitecture: Architecture.current,
linkModePreference: LinkModePreference.static,
cCompiler: cCompiler,
),
);
final buildInput = buildInputBuilder.build();
final buildOutput = BuildOutputBuilder();
// Drop a leftover object file into the build output directory before the
// build runs, simulating an object file left behind by an earlier build.
// `CBuilder.run` creates this directory with `create(recursive: true)`,
// which does not remove existing contents, so the stray file is still
// present when the archiver runs. It is deliberately not a valid COFF
// file, so `lib.exe` fails on it (`LNK1107`) if it is ever passed `*.obj`.
final outputDirectory = buildInput.outputDirectory;
await Directory.fromUri(outputDirectory).create(recursive: true);
await File.fromUri(
outputDirectory.resolve('stray.obj'),
).writeAsString('not a valid object file');
final cbuilder = CBuilder.library(
name: name,
assetName: name,
sources: [addCUri.toFilePath()],
buildMode: .release,
);
// Throws on the unpatched (`*.obj`) code: `lib.exe` picks up `stray.obj`
// and fails with `fatal error LNK1107: invalid or corrupt file`.
await cbuilder.run(input: buildInput, output: buildOutput, logger: logger);
final libUri = outputDirectory.resolve(
OS.windows.libraryFileName(name, StaticLinking()),
);
expect(await File.fromUri(libUri).exists(), isTrue);
});
}