How to use local module within a Project directory?

When I’m not in a Project directory, I can make a file like so:

# M1.jl
module M1
    export M1Type
    struct M1Type
        data::Real
    end
end

and then from that directory:

push!(LOAD_PATH, pwd()

and then

using M1

and that all works just fine. But if I’m in a Project directory (I have a Project.toml file), I can’t figure out how to use M1 in a similar way.

julia> using M1
ERROR: ArgumentError: Package M1 not found in current path:
- Run `import Pkg; Pkg.add("M1")` to install the M1 package.

I’ve tried to activate the local project and ]add M1, ]add ./M1.jl, ]add /full/path/to/M1.jl, etc., but I’m reasonably sure that that’s just for packages, not local modules, and it just says:

ERROR: The following package names could not be resolved:
 * M1 (not found in project, manifest or registry)
Please specify by known `name=uuid`.

hence I don’t know how to make Julia “know” about my local M1 module when I’m in a Project directory.

(I know I can include("M1.jl"), but that does something a little different, such that anything that needs M1 needs to know where it’s loaded.)

In How to load a module from the current directory?, the “solution” was to delete the Project.toml file. Not what I’m looking to do! :slight_smile:

I feel like I’ve read over the Module and Code Loading sections of the doc a bunch without figuring this out.

2 Likes

dev path/to/M1? where M1 has a Project file itself. If M1 is not a package then do indeed use include.

1 Like

Thanks for the quick reply! I’m unclear why that’s different when I’m in a Project directory than when I’m in a bare directory. It makes simple organizational tasks sort of strange, or I’m missing the right idiom for it perhaps.

As I understand it, in a bare directory, this works fine:

# M1.jl
module M1
    export M1Type
    struct M1Type
        data::Real
    end
end

# M2.jl
module M2
    using M1
    function f(x::M1Type)
        ...
    end
end

# At the REPL
using M1
using M2
f(M1Type(1))

On the other hand, the “Include” method forces M2 to have to know where M1 was loaded:

# M2.jl
module M2
    using ..M1 # How does M2 know where this was loaded, except that I've now hard-coded it?
    function f(x::M1Type)
        ...
    end
end

# At the REPL
include("M1.jl")
using M1
include("M2.jl")
using M2

so this is a fundamentally different way to organize my code, where modules now need to know where other modules are loaded, just due to the fact that I’m in a Project directory, if I understand correctly.

Is that correct?

I think there are some things in the loading of code that were put there to make the transition between the old package manager without Project files (0.6) to the new package manager (0.7) easier.

I would advise against touching LOAD_PATH and just use packages and dev for local code you want to reuse.

3 Likes

I think I follow you. I’m happy to leave the LOAD_PATH alone; I never actually liked that solution. You’re proposing to “upgrade” any local modules that need to be used in numerous places to full packages (with Project.toml files), and ]add them to the active project by their local paths.

Alternately, I might stop breaking things down into several little modules and instead make one larger module and just include local files with implementation details, such as:

# M1.jl
struct M1Type # Note that there's no module this time.
    data::Real
end

# M2.jl
function f(x::M1Type)
    ....
end

# Elsewhere
module M3
    include("M1.jl")
    include("M2.jl")
    f(MyType(3)) # Use the code for something.
end

Previously, when I needed to break functionality down, I was defaulting to putting that functionality in a bunch of little modules, but I was finding that that didn’t work once upgrading my “directory with a bunch of .jl files in it” to a “Project” directory. I couldn’t tell what I was doing wrong. Simply breaking the functionality down into individual files but not modules is probably the cleanest thing here, as far as I can tell.

Yes, except probably use ]dev

Thanks @kristoffer.carlsson!

Hi @kristoffer.carlsson, I know this is old, but as I’ve grown with this approach, there’s a new problem: "Unsatisfiable requirements" when local packages use local packages

dev the packages in the right order or possibly together. Or if you have more than a few local packages to juggle with, consider taking the next step and make your own registry with LocalRegistry. Alternatively, if they are of general interest, register them in the General registry.

2 Likes

As I understand it, in a bare directory, this works fine:

# M1.jl
module M1
    export M1Type
    struct M1Type
        data::Real
    end
end

# M2.jl
module M2
    using M1
    function f(x::M1Type)
        ...
    end
end

# At the REPL
using M1
using M2
f(M1Type(1))

This code doesn’t seem to compile anymore. I get the error UndefVarError: M1 not defined, even when I write using .M1 instead.

Anyone know how to do this?

Hi @peterj, I think your first using M1 needs to be using ..M1 (from the parent module). Your second using M1 needs to be using .M1 (for a local module), and the same for M2. Also, note that M2 does not export f presently, so you’ll need to add that.

Thanks, I didn’t know about the .. thing

I still get an error, actually the error I was trying to reproduce when I found this thread!

# M1.jl
module M1
    export M1Type
    struct M1Type
        data::Real
    end
end

# M2.jl
module M2
    using ..M1
    export f
    function f(x::M1Type)
        
    end
end

# At the REPL
using .M1
using .M2
f(M1Type(1))

gives the error MethodError: no method matching f(::M1Type) Closest candidates are: f(::Main.M1.M1Type) Stacktrace:..

Should I start a new thread about this?

Hi @peterj, I copied and pasted your code above into a .jl file and included it with no error. That was Julia v1.3. Maybe you should start a new thread and include the version of Julia?

Oh! Interesting. Yes this is julia v1.4. I shall do that.