Question about require directive

Hi, I have a question. For some needs in my project, it’s mandatory for me to require a file, but outside my project folder. How can I do that ?

I would like to use absolute path

You can technically do that. Just use the absolute path instead of a relative one as argument to require.
It’s probably a bad idea, though.
What do you think you need this for?

No I tried, but unfortunately it doesn’t work. The require instruction is always relative to the current path.
But no worries, I think I found maybe a better way to do what I need

Why crystal developers choose to restrict the require path to a relative path ? For security purpose ? I’m not sure to understand

Just to explain, when my software generate a task, it generate a crystal file, will interpreted by the crystal interpreter

I don’t think security is a factor (macro code and the program itself can still read any file on disk that the user has access to), but scoping required files to your project’s directory is important for portability. If my project expected a file outside the project’s directory, it would mean that my code can likely only be compiled on my machine — there is no guarantee that anyone else would have that Crystal source file in the same place on their hard drive when they pull down the project code. So it’s likely more that the functionality you’re asking about wouldn’t be useful (and would actually be confusing) for the nearly every use case.

If that’s what you really want, you can still achieve that through macros. You can put this somewhere in your app’s bootstrapping code (because macros have to be defined before they’re used):

macro require_absolute(path)
  {{run "require_absolute", path}}
end

Then in require_absolute.cr:

puts File.read(ARGV[0])

The run macro compiles the given Crystal file and pipes its stdout in place into your Crystal code. This is how ECR templates work.

One thing to keep in mind, though, is that an error in that file may not have as clean a stack trace as something loaded via require. I think there’s a way to improve those stack traces but I’m not 100% sure.

2 Likes

Probably better to just use Crystal::Macros - Crystal 1.7.2 since you just want the contents of the file and aren’t doing anything more complex.

{{ read_file "/path/to/file.cr" }}

Using the run macro isn’t great for compile times.

2 Likes

Ahh, I didn’t realize read_file was there! Nice!

This is true on first compile, but it’ll use a cached version on subsequent compilations. Might not be true in CI, but a few extra seconds of CI time usually isn’t very noticeable. :smile:

So I did a test following of your advices.

I have a file located here: /home/zohran/Documents/Programmation/ISM/ISM/Default/Path.cr
It content:

module ISM

    module Default

        module Path
            
            IsmDirectory = "ism/"
            BinaryDirectory = "bin/"
            ToolsDirectory = "tools/"
            SourcesDirectory = "sources/"
            RuntimeDataDirectory = "var/run/#{IsmDirectory}"
            TemporaryDirectory = "tmp/#{IsmDirectory}"
            SettingsDirectory = "etc/#{IsmDirectory}"
            LogsDirectory = "var/log/#{IsmDirectory}"
            LibraryDirectory = "usr/share/#{IsmDirectory}"
            PortsDirectory = "#{RuntimeDataDirectory}ports/"
            SoftwaresDirectory = "#{RuntimeDataDirectory}softwares/"
            InstalledSoftwaresDirectory = "#{RuntimeDataDirectory}installedsoftwares/"
            SettingsSoftwaresDirectory = "#{SettingsDirectory}softwares/"
            BuiltSoftwaresDirectory = "#{TemporaryDirectory}builtsoftwares/"

        end

    end

end

I made an another file named test.cr at: /home/zohran/test.cr
It content:

{{ read_file "/home/zohran/Documents/Programmation/ISM/ISM/Default/Path.cr" }}

puts ISM::Default::Path::BinaryDirectory

But when I run it, I have this error:

zohran@alienware-m17-r3 ~ $ crystal test.cr 
Showing last frame. Use --error-trace for full trace.

In test.cr:3:6

 3 | puts ISM::Default::Path::BinaryDirectory
          ^----------------------------------
Error: undefined constant ISM::Default::Path::BinaryDirectory

Try like {{ read_file("/home/zohran/Documents/Programmation/ISM/ISM/Default/Path.cr").id }}

read_file returns a StringLiteral so have to use #id to get a MacroId so its treated as actual code.

2 Likes

Oh yeah perfect ! Thanks a lot
You really saved my life

I never thought i could require a file in a absolute path.

But, i consider we should improve the error output when user try to do it.

 ╰─ $ cr run 1.cr 
Showing last frame. Use --error-trace for full trace.

In 1.cr:1:1

 1 | require "/home/zw963/Crystal/play/2.cr"
     ^
Error: can't find file '/home/zw963/Crystal/play/2.cr'

If you're trying to require a shard:
- Did you remember to run `shards install`?
- Did you make sure you're running the compiler in the same directory as your shard.yml?

 ╭─ 18:00  zw963 ⮀ ~/Crystal/play ⮀ ➦ ruby-3.2.1 
 ╰─ $ 1  file /home/zw963/Crystal/play/2.cr
/home/zw963/Crystal/play/2.cr: ASCII text