Skip to content

Ruby Sourcefiles and Libraries

datanorris edited this page Apr 4, 2016 · 6 revisions

Ruby applications can be comprised of multiple Ruby sourcefiles, and they can access objects made available by Ruby libraries.

Ruby has a very simple mechanism to support this - Ruby code can use APIs to load and execute other Ruby files. These other files then may perform tasks, such as creating classes, modules, methods and constants. Once the loaded file completes executing, the original Ruby code resumes and it can use the objects that were created.

In addition, there are API methods such as require which will load (and execute) a file, but then if the file is require'd again it will not reload the file. This is so multiple sourcefiles can "require" that a certain file be loaded, and the effect will be that the file is only loaded once. It is typical for Ruby sourcefiles to begin with require method calls, effectively declaring what other files need to be loaded as a prerequisite.

There are 2 types of file that can be loaded in Ruby:

  • a Ruby sourcefile, whose name must end in ".rb"
  • a Ruby extension library, whose name must end with ".dll" on Windows platforms or ".so" on everything else. These components are typically written in C, conform to certain requirements for Ruby extension libraries, and when executed perform tasks (such as creating objects in the Ruby environment) using the Ruby C API - creating extension libraries is beyond the scope of this document.

A basic multi-file Ruby application therefore consists of a designated initial sourcefile, which is to be executed directly and is called the "main" sourcefile, and some other Ruby sourcefiles which are loaded in the course of executing the initial file (usually via require or related APIs).

A basic Ruby library consists of some Ruby sourcefiles or extension libraries that are intended to be require'd in order to enable their features, and perhaps some other sourcefiles that are loaded in the course of the library files' execution. Ruby has a "standard library" with this structure, which is pre-installed with Ruby.

Load file name resolution

Ruby resolves load file names very similarly to the way that command-line commands are resolved in unix or Windows environments.

There exists a Ruby "load path" which is a list of filesystem directories to be potentially searched when loading a file. The load path is defined in various ways when the Ruby environment is started up (e.g. via hard-coded paths, command-line switches and environment variables). There exist no core APIs to modify a load path during Ruby execution, although a library extension could provide such features.

Load file names (and, for that matter, load path names) may be absolute or relative filesystem paths as supported by the underlying operating system, including UNC paths and NTFS streams in Windows environments. Ruby also supports expanding "~" or "~name", if it's the first path element, into the current user's or named user's home directory.

The "standard" resolution procedure for load file names is as follows:

  • If the file name is an absolute path (or home directory relative path), it resolves directly
  • Otherwise, if it is explicitly relative to the current working directory (i.e. its first path element is "." or ".."), it is resolved against the current working directory
  • Otherwise, resolution is attempted against each load path in turn until an openable file is found.

If load path names are relative (even explicitly), they are resolved against the current working directory.

The require APIs permit you to specify load file names with no file extension, and they will search for matching files with ".rb" (by preference) or ".dll"/".so" extensions.

There exist the usual complications on Windows - relative paths specifying a drive letter (e.g. D:file.rb) are relative the current working directory on that drive or potentially load paths on that drive, and relative paths beginning with a "" or "/" are relative to the current working drive or a load path's drive or UNC share.

On Windows environments, paths may use "/" interchangeably with "", and load file names ending in ".so" will also search for ".dll" files. Therefore, it is recommended to use "/" and ".so" rather than "" and ".dll" for portability with non-Windows platforms.

There is a require_relative API method which is a wrapper around normal require functionality - it resolves a relative load file name against the location of the currently executing Ruby sourcefile before requiring it.

Executing Ruby sourcefiles

When a Ruby sourcefile is loaded (other than the main sourcefile) its execution context is similar to that of the main sourcefile, except that it has its own local variable context. A sourcefile can also be loaded with a "wrap=true" option which modifies its self and class reference stack. See the Execution Context section for details.

When a load API method is called to load a file, it will wait until the file completes executing before it returns.

Require mechanism

Ruby maintains an Array of files that have been require'd, which can be accessed via $LOADED_FEATURES. With a couple of Ruby-internal exceptions, they are absolute paths to the loaded files. It also maintains an internal list of files currently in the process of loading.

When a file is require'd, Ruby will determine whether the file is loaded, loading or not previously require'd with the following steps, stopping on the first successful match:

  • Search the loaded and loading lists for the load file name as-is, or with file extensions added if the load file name doesn't have one
  • Search the loaded and loading lists for the load file name appended to each of the load paths, possibly with file extensions added if the load file name doesn't have one
  • Locate the file in the filesystem with the resolution procedure above (including adding file extensions), producing an absolute path to the file
  • Take certain steps to canonicalize the path:
  • On Windows, convert "" to "/"
  • Remove excess path separators, and "." and ".." path elements
  • On Windows, convert the file name to it's proper long file name and remove any reference to the ":$DATA" stream attribute
  • Search the loaded and loading lists again for the absolute path

In general, require APIs will return true if the file was successfully loaded, or false if it was not loaded because it had already been loaded or is currently in the process of loading. Specifically:

  • If the load file has been loaded, it returns false
  • If the load file has not been previously require'd, it loads the file
  • If the load file is currently in the process of loading:
  • If it's being loaded by the current Ruby thread, it returns false
  • If it's being loaded by another Ruby thread, the current thread will wait for the other to complete the load (either successfully or unsuccessfully). After waiting, if the file has been loaded it returns false, otherwise (if the other thread was unsuccessful) it loads the file.

When a require API loads a file, it:

  • Adds the file to the loading list
  • Loads and executes the file
  • If an exception was thrown during this process, the load is considered unsuccessful. The file is removed from the loading list and the exception is propagated - it is not added to the loaded list
  • Removes the file from the loading list and adds it to the loaded list
  • Returns true

Load API

  • to_path()

Method InstructionSequence eval iseq_eval Singleton method InstructionSequence compile iseq_s_compile Singleton method InstructionSequence new iseq_s_compile Singleton method InstructionSequence compile_file iseq_s_compile_file Virtual global 0 $: load_path_getter Alias global $: $-I Alias global $: $LOAD_PATH Virtual global 0 $\ get_loaded_features Virtual global 0 $LOADED_FEATURES get_loaded_features Module method Kernel load rb_f_load Module method Kernel require rb_f_require Module method Kernel require_relative rb_f_require_relative

Clone this wiki locally