Include command inconsistent with isfile result

I am having trouble to include a file. More precisely isfile(fi) returns true, but include(fi) errors
ERROR: LoadError: could not open file C:\temp\a\SomePackage\src\src\file.jl

I created an MWE here.
I simply run julia --startup-file=no --track-allocation=user mwe_include_error.jl in PowerShell (from the src directory)

Is this expected not to work?
It is quite irritating that isfile returns true, but the include command fails.

You example works for me:

julia> include(fi)
hello there

Are you on Windows?
Any idea why it fails for me?

have you actually done the same call (i.e. run julia with the argument mwe_include_error.jl)?

No, I didn’t, I get the same as you. From the docstring of include:

  include(path::AbstractString)

  Evaluate the contents of the input source file in the global scope
  of the containing module. Every module (except those defined with
  baremodule) has its own 1-argument definition of include, which
  evaluates the file in that module. Returns the result of the last
  evaluated expression of the input file. During including, a task-local
  include path is set to the directory containing the file. Nested calls to
  include will search relative to that path. This function is typically
  used to load source interactively, or to combine files in packages
  that are broken into multiple source files.

  Use Base.include to evaluate a file into another module.

I guess what is happening here is that when you execute julia file.jl there will be a nested include, and when you include from file.jl paths are relative that file.

2 Likes

I would vote for include and isfile to be consistent in the way that relative paths are resolved.
Would you agree?

Nah, you probably want the current behavior. You can work with absolute paths instead of relative paths.

1 Like

Well is there an easy way to make the path absolute?
Notably I am handing the same argument (a relative path) to isfile and include. But it is treated differently.

help?> abspath

  abspath(path::AbstractString) -> AbstractString

  Convert a path to an absolute path by adding the current directory
  if necessary. Also normalizes the path as in normpath.
1 Like

thanks this works.

I did not execute julia file.jl
I ran julia --startup-file=no .\mwe_include_error.jl
Have you tried that?

file.jl was just a placeholder.

I feel like your “model” is a little wrong. Normally julia files are in specific locations. So if a file does:

include("../file1.jl")

It expects to find file1.jl in the parent directory of the currently executing file…not in the parent directory of the current path. Otherwise one file changing the current directory would break all the includes for other files. i.e. If someone did this:

main.jl:
    include("f1.jl")
    include("f2.jl")
    include("f3.jl")
f1.jl:
    cd("c:\\")
f2.jl
   cd("c:\\temp")
f3.jl:
  println("hello")

This would just not work unless main.jl knows that f1.jl will change the directory, and that f2.jl will also change the directory. What it would create is a nightmare of “file not found” situations where you are going to have to display the current directory before every include just to make sure the current directory is what you expected before you do the include.

I would suggest looking into include_string where the file can be loaded first with something like read(filename, String) then passed to include_string. By using read the found/not found behavior of isfile will match with read.

2 Likes

I think you missed the fact that include operates relative to @__DIR__, which has nothing to do with the “current directory”, which is essentially a global state. Cf

$ pwd
/home/tamas

$ cat /tmp/test.jl 
@info "working directory" pwd()
@info "directory of file" @__DIR__

$ julia /tmp/test.jl
┌ Info: working directory
└   pwd() = "/home/tamas"
┌ Info: directory of file
└   #= /tmp/test.jl:2 =# @__DIR__ = "/tmp"

As explained by @pixel27, conflating the two would have obvious disadvantages — global state should be avoided unless absolutely necessary.

1 Like

So let’s say I have a number of functions (readdlm, writedlm, CSV.read, isfile, include, rm, …).
Is there an easy way to find out whether these work relative to „current dir“ or @__DIR__?

I think that basically everything that uses built-in I/O (think of open) will resolve paths relative to the current directory if necessary, but relying on this is not very good style.

You should always provide full paths if possible. If you are loading a lot of files from your package directory, it is worth it to define something like

project_path(parts...) = normpath(joinpath(@__DIR__, "..", parts...))

in one of the files in src/ and then use it to construct paths.

Thank you.
I think my confusion was ‘initiated’ by the fact that I have used the include command in an interactive VS Code session. In that case, I think, the include function is not relative to the file its in (as there is no such file), but relative to the working dir.

Only code loading (include) is relative to the current source file. Everything else is a normal file operation and works relative to the current working directory.