Skip to content

Feature request: scan subdirs for updated versions/projects #1874

@timholy

Description

@timholy

Summary

For repositories that have more than one package on them, and some of the packages depend on others, CI runs into trouble because julia --project picks the "umbrella" Project.toml files which sets a new version just for that one package. If the umbrella package depends on new versions of the subdirectory packages, the resolver doesn't know that it can get them from the current PR.

Current workaround

If you're running CI with GitHub Actions, add something like

defaults:
  run:
    shell: bash
...

    steps:
      ...
      - run: julia --project -e 'using Pkg; Pkg.develop([PackageSpec(path="SubDirA"),
                                                         PackageSpec(path="SubDirB"), ...])'

Travis users should add something similar.

Reproducer

Following on the lengthy discussion in #1251, I thought since all the people who actually know Pkg internals were confused about what I was asking, I would create something that is close to an actual test case (and therefore may assist in developing the requested feature). The executive summary here is that Pkg does not yet support updating its awareness of sub-package versions based on a new pull request.

Setup: create a blank directory to do this test in, I chose /tmp/pkgs. Save the script below as /tmp/pkgs/generate_test.jl, make sure you've cd /tmp/pkgs, launch Julia as JULIA_DEPOT_PATH=/tmp/pkgs julia --startup-file=no (I was using Julia 1.6 just to get the latest) and then include the script. Here's the script:

function writelines(path, lines)
    open(path, "w") do io
        for line in lines
            println(io, line)
        end
    end
end

# Create a package BigPkg with a subdirectory SubPkg
# Make BigPkg depend on SubPkg
# Register it
using Pkg
Pkg.add("LocalRegistry")
using LocalRegistry
regpath = joinpath(@__DIR__, "registries", "MyRegistry")
create_registry("MyRegistry", regpath)
# Pkg.Registry.add(regpath)
devpath = joinpath(@__DIR__, "dev")
mkpath(devpath)
bigpkgpath = joinpath(devpath, "BigPkg")
subpkgpath = joinpath(bigpkgpath, "SubPkg")
Pkg.generate(bigpkgpath)
Pkg.generate(subpkgpath)
Pkg.develop(path=bigpkgpath)
Pkg.develop(path=subpkgpath)
cd(bigpkgpath) do
    run(`git init`)
    run(`git add Project.toml src/BigPkg.jl SubPkg/Project.toml SubPkg/src/SubPkg.jl`)
    run(`git commit -m "Skeleton"`)

    Pkg.activate(".")
    Pkg.add(path=".", subdir="SubPkg")
    lines = readlines("Project.toml")
    append!(lines, ["", "[compat]", "SubPkg = \"0.1\""])
    writelines("Project.toml", lines)
    lines = readlines("src/BigPkg.jl")
    insert!(lines, 2, "using SubPkg")
    writelines("src/BigPkg.jl", lines)
    Pkg.activate()
    Pkg.resolve()
    run(`git add Project.toml src/BigPkg.jl`)
    run(`git commit -m "Add SubPkg dependency"`)
end
using BigPkg
using SubPkg
register(SubPkg, "MyRegistry"; repo=bigpkgpath)
register(BigPkg, "MyRegistry"; repo=bigpkgpath)

# OK, now both packages are registered at v0.1.
# Let's add a new feature to SubPkg and bump its version
# to 0.2. Simultaneously, let's require the new feature
# in BigPkg and bump its version to 0.2.
cd(subpkgpath) do
    lines = readlines("src/SubPkg.jl")
    insert!(lines, 2, "export f")
    insert!(lines, 3, "f() = 1")
    writelines("src/SubPkg.jl", lines)
    lines = readlines("Project.toml")
    idx = findfirst(str->startswith(str, "version"), lines)
    lines[idx] = "version = \"0.2.0\""
    writelines("Project.toml", lines)
end
cd(bigpkgpath) do
    lines = readlines("src/BigPkg.jl")
    insert!(lines, 3, "g() = f()")
    writelines("src/BigPkg.jl", lines)
    lines = readlines("Project.toml")
    idx = findfirst(str->startswith(str, "version"), lines)
    lines[idx] = "version = \"0.2.0\""
    lines[end] = "SubPkg = \"0.2\""
    writelines("Project.toml", lines)
    mkdir("test")
    open("test/runtests.jl", "w") do io
        println(io, """
        using BigPkg, Test
        @test BigPkg.g() == 1
        """)
    end

    run(`git add Project.toml src/BigPkg.jl SubPkg/Project.toml SubPkg/src/SubPkg.jl test/runtests.jl`)
    run(`git commit -m "Version 0.2"`)
end

# Remove packages in preparation for simulating what happens when we
# submit a PR and CI runs on Travis
Pkg.rm("SubPkg")
Pkg.rm("BigPkg")

Then I navigate to dev and do this:

tim@diva:/tmp/pkgs/dev$ JULIA_DEPOT_PATH=/tmp/pkgs JL_PKG=BigPkg julia-master --startup-file=no -e 'using Pkg; Pkg.build(verbose=true)'

For some reason, executing the tests as in the default travis script doesn't seem to work for me, but I can do this:

tim@diva:/tmp/pkgs/dev/BigPkg$ JULIA_DEPOT_PATH=/tmp/pkgs julia-master --startup-file=no --project -e 'using Pkg; Pkg.test("BigPkg", coverage=false)'
    Testing BigPkg
┌ Error: Pkg.Resolve.ResolverError("empty intersection between SubPkg@0.1.0 and project compatibility 0.2", nothing)
└ @ Pkg.Operations ~/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1410
ERROR: empty intersection between SubPkg@0.1.0 and project compatibility 0.2
Stacktrace:
 [1] resolve_versions!(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:370
 [2] up(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}, ::Pkg.Types.UpgradeLevel) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1209
 [3] up(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}; level::Pkg.Types.UpgradeLevel, mode::Pkg.Types.PackageMode, update_registry::Bool, kwargs::Base.Iterators.Pairs{Symbol,Base.DevNull,Tuple{Symbol},NamedTuple{(:io,),Tuple{Base.DevNull}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:245
 [4] up(::Pkg.Types.Context; kwargs::Base.Iterators.Pairs{Symbol,Any,NTuple{4,Symbol},NamedTuple{(:level, :mode, :update_registry, :io),Tuple{Pkg.Types.UpgradeLevel,Pkg.Types.PackageMode,Bool,Base.DevNull}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:68
 [5] resolve(::Pkg.Types.Context; kwargs::Base.Iterators.Pairs{Symbol,Base.DevNull,Tuple{Symbol},NamedTuple{(:io,),Tuple{Base.DevNull}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:251
 [6] (::Pkg.Operations.var"#98#102"{String,Pkg.Operations.var"#108#110"{Bool,Cmd,Cmd,Nothing,Pkg.Types.Context,Array{Tuple{String,Base.Process},1},String,Pkg.Types.PackageSpec},Pkg.Types.PackageSpec})() at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1412
 [7] with_temp_env(::Pkg.Operations.var"#98#102"{String,Pkg.Operations.var"#108#110"{Bool,Cmd,Cmd,Nothing,Pkg.Types.Context,Array{Tuple{String,Base.Process},1},String,Pkg.Types.PackageSpec},Pkg.Types.PackageSpec}, ::String) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1331
 [8] (::Pkg.Operations.var"#97#101"{Pkg.Operations.var"#108#110"{Bool,Cmd,Cmd,Nothing,Pkg.Types.Context,Array{Tuple{String,Base.Process},1},String,Pkg.Types.PackageSpec},Pkg.Types.Context,Pkg.Types.PackageSpec,String,Pkg.Types.Project,String})(::String) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1402
 [9] mktempdir(::Pkg.Operations.var"#97#101"{Pkg.Operations.var"#108#110"{Bool,Cmd,Cmd,Nothing,Pkg.Types.Context,Array{Tuple{String,Base.Process},1},String,Pkg.Types.PackageSpec},Pkg.Types.Context,Pkg.Types.PackageSpec,String,Pkg.Types.Project,String}, ::String; prefix::String) at ./file.jl:682
 [10] mktempdir(::Function, ::String) at ./file.jl:680 (repeats 2 times)
 [11] sandbox(::Function, ::Pkg.Types.Context, ::Pkg.Types.PackageSpec, ::String, ::String, ::Pkg.Types.Project) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1369
 [12] test(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}; coverage::Bool, julia_args::Cmd, test_args::Cmd, test_fn::Nothing) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1533
 [13] test(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}; coverage::Bool, test_fn::Nothing, julia_args::Cmd, test_args::Cmd, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:327
 [14] #test#61 at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:67 [inlined]
 [15] #test#60 at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:66 [inlined]
 [16] test(::String; kwargs::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:coverage,),Tuple{Bool}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:65
 [17] top-level scope at none:1
caused by [exception 1]
empty intersection between SubPkg@0.1.0 and project compatibility 0.2
Stacktrace:
 [1] resolve_versions!(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:370
 [2] up(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}, ::Pkg.Types.UpgradeLevel) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1209
 [3] up(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}; level::Pkg.Types.UpgradeLevel, mode::Pkg.Types.PackageMode, update_registry::Bool, kwargs::Base.Iterators.Pairs{Symbol,Base.DevNull,Tuple{Symbol},NamedTuple{(:io,),Tuple{Base.DevNull}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:245
 [4] up(::Pkg.Types.Context; kwargs::Base.Iterators.Pairs{Symbol,Any,NTuple{4,Symbol},NamedTuple{(:level, :mode, :update_registry, :io),Tuple{Pkg.Types.UpgradeLevel,Pkg.Types.PackageMode,Bool,Base.DevNull}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:68
 [5] resolve(::Pkg.Types.Context; kwargs::Base.Iterators.Pairs{Symbol,Base.DevNull,Tuple{Symbol},NamedTuple{(:io,),Tuple{Base.DevNull}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:251
 [6] resolve(; kwargs::Base.Iterators.Pairs{Symbol,Base.DevNull,Tuple{Symbol},NamedTuple{(:io,),Tuple{Base.DevNull}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:249
 [7] (::Pkg.Operations.var"#98#102"{String,Pkg.Operations.var"#108#110"{Bool,Cmd,Cmd,Nothing,Pkg.Types.Context,Array{Tuple{String,Base.Process},1},String,Pkg.Types.PackageSpec},Pkg.Types.PackageSpec})() at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1407
 [8] with_temp_env(::Pkg.Operations.var"#98#102"{String,Pkg.Operations.var"#108#110"{Bool,Cmd,Cmd,Nothing,Pkg.Types.Context,Array{Tuple{String,Base.Process},1},String,Pkg.Types.PackageSpec},Pkg.Types.PackageSpec}, ::String) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1331
 [9] (::Pkg.Operations.var"#97#101"{Pkg.Operations.var"#108#110"{Bool,Cmd,Cmd,Nothing,Pkg.Types.Context,Array{Tuple{String,Base.Process},1},String,Pkg.Types.PackageSpec},Pkg.Types.Context,Pkg.Types.PackageSpec,String,Pkg.Types.Project,String})(::String) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1402
 [10] mktempdir(::Pkg.Operations.var"#97#101"{Pkg.Operations.var"#108#110"{Bool,Cmd,Cmd,Nothing,Pkg.Types.Context,Array{Tuple{String,Base.Process},1},String,Pkg.Types.PackageSpec},Pkg.Types.Context,Pkg.Types.PackageSpec,String,Pkg.Types.Project,String}, ::String; prefix::String) at ./file.jl:682
 [11] mktempdir(::Function, ::String) at ./file.jl:680 (repeats 2 times)
 [12] sandbox(::Function, ::Pkg.Types.Context, ::Pkg.Types.PackageSpec, ::String, ::String, ::Pkg.Types.Project) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1369
 [13] test(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}; coverage::Bool, julia_args::Cmd, test_args::Cmd, test_fn::Nothing) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/Operations.jl:1533
 [14] test(::Pkg.Types.Context, ::Array{Pkg.Types.PackageSpec,1}; coverage::Bool, test_fn::Nothing, julia_args::Cmd, test_args::Cmd, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:327
 [15] #test#61 at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:67 [inlined]
 [16] #test#60 at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:66 [inlined]
 [17] test(::String; kwargs::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:coverage,),Tuple{Bool}}}) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/Pkg/src/API.jl:65
 [18] top-level scope at none:1

So, the obvious problem is that SubPkg is a dependency of BigPkg, and this new version of BigPkg requires SubPkg@0.2, but SubPkg@0.2 has not been registered yet.

In #1251, @fredrikekre correctly points out that this is no different from how it works normally. But when you have long dependency chains, how it works normally is really painful. When you have a single git repo, you would like to be able to leverage that organization to develop all the packages in concert. To the developer, the most sensible unit is the repo, not the package.

What do we need? I think the only thing missing is some command that will update the session's notion of what package versions are available by scanning the Project.toml files of all packages listed in the [deps] section of BigPkg. If it does that, and then discovers that in the checked-out copy, lo and behold there is a SubPkg@0.2, then it can proceed and try everything together.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions