Skip to content

fix: workspace executables omit their own objects when the package also has a shared target #760

Description

@julixian

Reproduction

A workspace member declares a shared-library target and an executable target over the same implementation:

repro/
  mcpp.toml
  player/
    mcpp.toml
    player.cppm
    impl.cpp
    main.cpp

Root mcpp.toml:

[workspace]
members = ["player"]

[workspace.package]
version = "0.1.0"
standard = "c++23"

[toolchain]
windows = "llvm@22.1.8"

player/mcpp.toml:

[package]
namespace = "probe"
name = "player"

[build]
sources = ["player.cppm", "impl.cpp"]

[targets.player_dll]
kind = "shared"

[targets.player]
kind = "bin"
main = "main.cpp"

player/player.cppm:

export module probe.player;
export int answer();

player/impl.cpp:

module probe.player;
int answer() { return 42; }

player/main.cpp:

#include <cstdio>
import probe.player;
int main() { std::printf("%d\n", answer()); }

Run mcpp build --workspace or mcpp build -p probe.player from the root.

Expected behavior

The executable links the member's implementation objects, as the documented compile-once model describes. It remains usable without its sibling player_dll.dll; a bin target should not acquire a runtime dependency on a sibling shared target simply because they belong to the same package.

Other packages consuming probe.player should continue to link its shared-library target. Static dependencies needed by the member's executable must also be included in that executable's own static closure.

Actual behavior

The shared library links, but the executable link omits the member's implementation objects and fails:

lld-link: error: undefined symbol: int __cdecl answer(void)
>>> referenced by player/main.cpp:3
>>>               obj/probe_player/main.o:(main)
clang++: error: linker command failed with exit code 1

The same failure was reproduced with a static module dependency under the producer and a second workspace member consuming the DLL. The executable response file contains its entry object, but not the producer's implementation objects.

Code path

src/build/plan.cppm records every package with a shared target in sharedDepPackages. The workspace-member executable loop then skips all compile units whose package is in that set, including its own package. The exclusion is appropriate for external consumers, but removes the executable's own implementation.

Adding the sibling DLL import library would hide the missing objects while changing the executable's semantics. The fix should preserve a self-contained executable and retain shared linkage for external consumers.

Environment

  • Windows x64.
  • mcpp 2026.10.3.1; current main also contains the same exclusion.
  • LLVM 22.1.8, MSVC sysroot 14.51.36231, Windows SDK 10.0.26100.0.
  • Default development profile; no LTO or export annotations are needed to reproduce this failure.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions