Skip to content

Problem with cyclic dependencies #6905

@mpilgrem

Description

@mpilgrem

Consider two packages, A and B. Both have a library and B has a test suite. If the library of A depends on B and the test suite of B depends on A, then:

  • stack build should work; and
  • stack test should also work. This is because, although if Stack built libraries and executables 'all-in-one` (as it normally does) there would be a 'cycle' (A depends on B, B depends on A), Stack is supposed to detect that and defer the building of B's test suite until after the libraries are built.

There is an integration test that is supposed to check for that: multi-test. However, I have discovered that:

  • if A is named myPackageA and B is named myPackageB, stack test fails; but
  • if A is named myPackageD and B is named myPackageC it passes.

That is, whether stack test fails or passes depends on the alphabetical ordering of the names of A and B.

When it fails, it fails with:

Error: [S-4804]
       Stack failed to construct a build plan.

       While constructing the build plan, Stack encountered the following errors:

       In the dependencies for myPackageB-0.0.0:
         * myPackageA dependency cycle detected: myPackageA, myPackageB, myPackageA
       The above is/are needed since myPackageB is a build target.

With A and B, stack --verbose --plan-in-log ... test includes (ignoring the base GHC boot package):

[debug] Constructing the build plan
[debug] (addDep) Package info for myPackageA: PIOnlySource PSFilePath ...
[debug] (installPackage) No test or bench component for myPackageA so doing an all-in-one
[debug] (addDep) Package info for myPackageB: PIOnlySource PSFilePath ... 
[debug] (checkCallStackAndAddDep) Detected cycle myPackageA: ["myPackageB","myPackageA"]
[debug] (installPackage) Before trying cyclic plan, resetting lib result map to: fromList []
[debug] (checkCallStackAndAddDep) Detected cycle myPackageA: ["myPackageB","myPackageA"]
[debug] (getCachedDepOrAddDep) Using cached result for myPackageB: Right (ADRToInstall ... allInOne = False

With D and C, stack --verbose --plan-in-log ... test includes (again, ignoring 'base') (annotated with [A] and [B] for clarity):

[debug] Constructing the build plan
[debug] (addDep) Package info for [B] myPackageC: ...
[debug] (addDep) Package info for [A] myPackageD: ...
[debug] (installPackage) No test or bench component for [A] myPackageD so doing an all-in-one build.
[debug] (checkCallStackAndAddDep) Detected cycle [B] myPackageC: [ [A] "myPackageD", [B] "myPackageC"]
[debug] (installPackage) Before trying cyclic plan, resetting lib result map to: fromList []
[debug] (addDep) Package info for [A] myPackageD: ...
[debug] (installPackage) No test or bench component for [A] myPackageD so doing an all-in-one build.
[debug] (getCachedDepOrAddDep) Using cached result for [B] myPackageC: ...
[debug] (getCachedDepOrAddDep) Using cached result for [A] myPackageD: ...

This does not appear to be a recent regression. I think it may have been present since at least Stack 2.1.1. EDIT: It may have been present since Stack 0.0.2:

EDIT: With additional debug information:

With A and B (ignoring 'base'):

[debug] Constructing the build plan
[debug] (constructPlan) Constructing for target myPackageA
[debug] (checkCallStackAndAddDep) Pushing myPackageA on to the call stack.
[debug] (addDep) Package info for myPackageA: PIOnlySource PSFilePath ...
[debug] (installPackage) No test or bench component for myPackageA so doing an all-in-one build.
[debug] (checkCallStackAndAddDep) Pushing myPackageB on to the call stack.
[debug] (addDep) Package info for myPackageB: PIOnlySource PSFilePath ...
[debug] (checkCallStackAndAddDep) Detected cycle myPackageA: ["myPackageB","myPackageA"]
[debug] (updateLibMap) Updating for: myPackageA (error)
[debug] (installPackage) Before trying cyclic plan, resetting lib result map to: fromList []
[debug] (updateLibMap) Updating for: myPackageB (ok)
[debug] (checkCallStackAndAddDep) Detected cycle myPackageA: ["myPackageB","myPackageA"]
[debug] (updateLibMap) Updating for: myPackageA (error)
[debug] (addFinal) Adding to construction output myPackageB (error)
[debug] (checkCallStackAndAddDep) Popped myPackageB from the call stack.
[debug] (updateLibMap) Updating for: myPackageB (ok)
[debug] (checkCallStackAndAddDep) Popped myPackageA from the call stack.
[debug] (updateLibMap) Updating for: myPackageA (ok)
[debug] (constructPlan) Constructing for target myPackageB
[debug] (getCachedDepOrAddDep) Using cached result for myPackageB: ...

With D and C (ignoring 'base'):

[debug] Constructing the build plan
[debug] (constructPlan) Constructing for target myPackageC
[debug] (checkCallStackAndAddDep) Pushing myPackageC on to the call stack.
[debug] (addDep) Package info for myPackageC: PIOnlySource PSFilePath ...
[debug] (checkCallStackAndAddDep) Pushing myPackageD on to the call stack.
[debug] (addDep) Package info for myPackageD: PIOnlySource PSFilePath ...
[debug] (installPackage) No test or bench component for myPackageD so doing an all-in-one build.
[debug] (checkCallStackAndAddDep) Detected cycle myPackageC: ["myPackageD","myPackageC"]
[debug] (updateLibMap) Updating for: myPackageC (error)
[debug] (checkCallStackAndAddDep) Popped myPackageD from the call stack.
[debug] (updateLibMap) Updating for: myPackageD (error)
[debug] (installPackage) Before trying cyclic plan, resetting lib result map to: fromList []
[debug] (updateLibMap) Updating for: myPackageC (ok)
[debug] (checkCallStackAndAddDep) Pushing myPackageD on to the call stack.
[debug] (addDep) Package info for myPackageD: PIOnlySource PSFilePath ...
[debug] (installPackage) No test or bench component for myPackageD so doing an all-in-one build.
[debug] (getCachedDepOrAddDep) Using cached result for myPackageC: ...
[debug] (checkCallStackAndAddDep) Popped myPackageD from the call stack.
[debug] (updateLibMap) Updating for: myPackageD (ok)
[debug] (addFinal) Adding to construction output myPackageC (ok)
[debug] (checkCallStackAndAddDep) Popped myPackageC from the call stack.
[debug] (updateLibMap) Updating for: myPackageC (ok)
[debug] (constructPlan) Constructing for target myPackageD
[debug] (getCachedDepOrAddDep) Using cached result for myPackageD: ...

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions