The Mismatch between pwd() and @__DIR__ and how it impacts packages like GLMakie

When I run Julia in a modularised structure in VSCode and try accessing files in a folder sibling the the folder of the source file, all within a root project directory, something interesting happens.

(All of the following had been evaluated using the shift+enter evaluation in VSCode, which interestingly gives different results from if I were to call the same lines within the REPL that opened up directly)

       
        
Printing pwd(): D:\Programming\Flapping Wing Flying Research

Printing @__DIR__: d:\Programming\Flapping Wing Flying Research\Simulations

including ../src/State_and_Conversions.jl

        
Loading ../Models/DragonFly/HighQuality/body.obj

ERROR: ArgumentError: No file exists at given path: ../Models/DragonFly/HighQuality/body.obj
Stacktrace:
 [1] checkpath_load(file::String)
   @ FileIO C:\Users\aryan\.julia\packages\FileIO\ePyCW\src\loadsave.jl:167
 [2] load(::String; options::@Kwargs{})
   @ FileIO C:\Users\aryan\.julia\packages\FileIO\ePyCW\src\loadsave.jl:110
 [3] load(::String)
   @ FileIO C:\Users\aryan\.julia\packages\FileIO\ePyCW\src\loadsave.jl:109
 [4] top-level scope
   @ d:\Programming\Flapping Wing Flying Research\Simulations\dragonfly.jl:19

Loading Models/DragonFly/HighQuality/body.png

┌ Warning: Output swatches are reduced due to the large size (7168×7168).
│ Load the ImageShow package for large images.
└ @ Colors C:\Users\aryan\.julia\packages\Colors\VFEJ1\src\display.jl:159      

changing directory to @__DIR__

        
trying to load ../Models/DragonFly/HighQuality/body.png again

┌ Warning: Output swatches are reduced due to the large size (7168×7168).
│ Load the ImageShow package for large images.
└ @ Colors C:\Users\aryan\.julia\packages\Colors\VFEJ1\src\display.jl:159      

julia> pwd()
"d:\\Programming\\Flapping Wing Flying Research\\Simulations"

Source:

using GLMakie

println("Printing pwd(): ", pwd())
println("Printing @__DIR__: ", @__DIR__)

println("including ../src/State_and_Conversions.jl")
# Import custom code and utility functions
include("../src/State_and_Conversions.jl")

println("Loading ../Models/DragonFly/HighQuality/body.obj")
load("../Models/DragonFly/HighQuality/body.obj") # Load the body model

println("Loading Models/DragonFly/HighQuality/body.png")
load("Models/DragonFly/HighQuality/body.png") # Load the body texture

println("changing directory to @__DIR__")
cd(@__DIR__) # Change directory to the current script's directory

println("trying to load ../Models/DragonFly/HighQuality/body.png again")
load("../Models/DragonFly/HighQuality/body.png") # Load the body texture

File Structure:

. (Root Folder)
├───Models
│   └───DragonFly
│      └───HighQuality
│             └───body.png 
├───Simulations
│      └───dragonfly.jl -> the current source file.
├───src
│      └───State_and_Conversions.jl


First, by default, pwd() and @__DIR__ output different things when called from a source file! pwd(), unlike @__DIR__ refers to the project root folder path, instead of the path of the current source directory being run.
(the same holds true if I say julia test.jl with the code)


The include function does use the @__DIR__ path as one would expect. However…

I suspect this makes the use of packages like GLMakie a little confusing when using functions like load which load a file (png, obj, etc) as an object. I am pretty confident from my testing (although not sure) that the load function in Makie has something to do with the pwd() path and not the @__DIR__ path. (either directly or through the indirect chain of JULIA_DEPOT_PATH or LOAD_PATH).

The workaround I found was to simply have changed your directory in the beginning to have the REPL session open up in the directory of the source file and not that of the project root.

Is there any better way to do this? Did I misinterpret something? What would be the intension of such a design?

So include is a special case, in general if you want to do something relative to the current file you should always use @__DIR__ explicitly, i.e. joinpath(@__DIR__, "my", "folder", "with", "file.png"). It’s just that include is easier to parse if it’s relative so iirc it has some special handling.

@__DIR__ is specifically the directory that contains the file that macro is used in.

But in general every file path, unless absolute, is relative to pwd(). This goes for any Julia library that loads code.

(An interesting note is that Documenter.jl is another exception to this - it uses some trickery in the makedocs and deploydocs functions)

2 Likes