Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CrystalPath.expand_paths, expand relative to compiler path #11030

9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@ SPEC_SOURCES := $(shell find spec -name '*.cr')
override FLAGS += $(if $(release),--release )$(if $(stats),--stats )$(if $(progress),--progress )$(if $(threads),--threads $(threads) )$(if $(debug),-d )$(if $(static),--static )$(if $(LDFLAGS),--link-flags="$(LDFLAGS)" )$(if $(target),--cross-compile --target $(target) )
SPEC_WARNINGS_OFF := --exclude-warnings spec/std --exclude-warnings spec/compiler
SPEC_FLAGS := $(if $(verbose),-v )$(if $(junit_output),--junit_output $(junit_output) )
CRYSTAL_CONFIG_LIBRARY_PATH := $(shell bin/crystal env CRYSTAL_LIBRARY_PATH 2> /dev/null)
CRYSTAL_CONFIG_LIBRARY_PATH := '$$ORIGIN/../lib/crystal'
CRYSTAL_CONFIG_BUILD_COMMIT := $(shell git rev-parse --short HEAD 2> /dev/null)
CRYSTAL_CONFIG_PATH := '$$ORIGIN/../share/crystal/src'
SOURCE_DATE_EPOCH := $(shell (git show -s --format=%ct HEAD || stat -c "%Y" Makefile || stat -f "%m" Makefile) 2> /dev/null)
EXPORTS := \
CRYSTAL_CONFIG_LIBRARY_PATH="$(CRYSTAL_CONFIG_LIBRARY_PATH)" \
CRYSTAL_CONFIG_BUILD_COMMIT="$(CRYSTAL_CONFIG_BUILD_COMMIT)" \
CRYSTAL_CONFIG_PATH=$(CRYSTAL_CONFIG_PATH) \
SOURCE_DATE_EPOCH="$(SOURCE_DATE_EPOCH)"
EXPORTS_BUILD := \
CRYSTAL_CONFIG_LIBRARY_PATH=$(CRYSTAL_CONFIG_LIBRARY_PATH)
SHELL = sh
LLVM_CONFIG := $(shell src/llvm/ext/find-llvm-config)
LLVM_EXT_DIR = src/llvm/ext
Expand Down Expand Up @@ -116,7 +119,7 @@ $(O)/compiler_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES)

$(O)/crystal: $(DEPS) $(SOURCES)
@mkdir -p $(O)
$(EXPORTS) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib
$(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib

$(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)/llvm_ext.cc
$(CXX) -c $(CXXFLAGS) -o $@ $< $(shell $(LLVM_CONFIG) --cxxflags)
Expand Down
12 changes: 7 additions & 5 deletions bin/crystal
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,13 @@ export CRYSTAL_HAS_WRAPPER=true

export CRYSTAL="${CRYSTAL:-"crystal"}"

if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ]; then
export CRYSTAL_CONFIG_LIBRARY_PATH="$(
export PATH="$(remove_path_item "$(remove_path_item "$PATH" "$SCRIPT_ROOT")" "bin")"
crystal env CRYSTAL_LIBRARY_PATH || echo ""
)"
if [ -z "$CRYSTAL_CONFIG_LIBRARY_PATH" ] || [ -z "$CRYSTAL_LIBRARY_PATH" ]; then
CRYSTAL_INSTALLED_LIBRARY_PATH="$(
export PATH="$(remove_path_item "$(remove_path_item "$PATH" "$SCRIPT_ROOT")" "bin")"
crystal env CRYSTAL_LIBRARY_PATH || echo ""
)"
export CRYSTAL_LIBRARY_PATH=${CRYSTAL_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH}
export CRYSTAL_CONFIG_LIBRARY_PATH=${CRYSTAL_CONFIG_LIBRARY_PATH:-$CRYSTAL_INSTALLED_LIBRARY_PATH}
fi

if [ -x "$CRYSTAL_DIR/crystal" ]; then
Expand Down
16 changes: 15 additions & 1 deletion man/crystal.1
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ Please see
.Sm "ENVIRONMENT VARIABLES".
.Pp
.It
.It Sy CRYSTAL_LIBRARY_PATH
Please see
.Sm "ENVIRONMENT VARIABLES".
.Pp
.It
.It Sy CRYSTAL_PATH
Please see
.Sm "ENVIRONMENT VARIABLES".
Expand Down Expand Up @@ -384,8 +389,17 @@ Show version.
Defines path where Crystal caches partial compilation results for faster subsequent builds. This path is also used to temporarily store executables when Crystal programs are run with 'crystal run' rather than 'crystal build'.
.Pp
.It
.It Sy CRYSTAL_LIBRARY_PATH
Defines paths where Crystal searches for (binary) libraries. Multiple paths can be separated by ":".
These paths are passed to the linker as `-L` flags.
.Pp
The pattern '$ORIGIN' at the start of the path expands to the directory where the compiler binary is located. For example, '$ORIGIN/../lib/crystal' resolves the standard library path relative to the compiler location in a generic way, independent of the absolute paths (assuming the relative location is correct).
.Pp
.It
.It Sy CRYSTAL_PATH
Defines paths where Crystal searches for required files.
Defines paths where Crystal searches for required source files. Multiple paths can be separated by ":".
.Pp
The pattern '$ORIGIN' at the start of the path expands to the directory where the compiler binary is located. For example, '$ORIGIN/../share/crystal/src' resolves the standard library path relative to the compiler location in a generic way, independent of the absolute paths (assuming the relative location is correct).
.Pp
.It
.It Sy CRYSTAL_OPTS
Expand Down
24 changes: 22 additions & 2 deletions spec/compiler/crystal_path/crystal_path_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require "spec/helpers/iterate"

private def assert_finds(search, results, relative_to = nil, path = __DIR__, file = __FILE__, line = __LINE__)
it "finds #{search.inspect}", file, line do
crystal_path = Crystal::CrystalPath.new(path)
crystal_path = Crystal::CrystalPath.new([path])
results = results.map { |result| ::Path[__DIR__, result].normalize.to_s }
Dir.cd(__DIR__) do
matches = crystal_path.find search, relative_to: relative_to
Expand All @@ -15,7 +15,7 @@ end

private def assert_doesnt_find(search, relative_to = nil, path = __DIR__, expected_relative_to = nil, file = __FILE__, line = __LINE__)
it "doesn't finds #{search.inspect}", file, line do
crystal_path = Crystal::CrystalPath.new(path)
crystal_path = Crystal::CrystalPath.new([path])
Dir.cd(__DIR__) do
error = expect_raises Crystal::CrystalPath::NotFoundError do
crystal_path.find search, relative_to: relative_to
Expand Down Expand Up @@ -188,4 +188,24 @@ describe Crystal::CrystalPath do
crystal_path.entries.should eq(%w(foo bar))
end
end

it ".expand_paths" do
paths = ["$ORIGIN/../foo"]
Crystal::CrystalPath.expand_paths(paths, "/usr/bin/")
paths.should eq ["/usr/bin/../foo"]
paths = ["./$ORIGIN/../foo"]
Crystal::CrystalPath.expand_paths(paths, "/usr/bin/")
paths.should eq ["./$ORIGIN/../foo"]
paths = ["$ORIGINfoo"]
Crystal::CrystalPath.expand_paths(paths, "/usr/bin/")
paths.should eq ["$ORIGINfoo"]
paths = ["lib", "$ORIGIN/../foo"]
Crystal::CrystalPath.expand_paths(paths, "/usr/bin/")
paths.should eq ["lib", "/usr/bin/../foo"]

paths = ["$ORIGIN/../foo"]
expect_raises(Exception, "Missing executable path to expand $ORIGIN path") do
Crystal::CrystalPath.expand_paths(paths, nil)
end
end
end
12 changes: 10 additions & 2 deletions src/compiler/crystal/codegen/link.cr
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,20 @@ module Crystal
end

class CrystalLibraryPath
def self.default_paths : Array(String)
paths = ENV.fetch("CRYSTAL_LIBRARY_PATH", Crystal::Config.library_path).split(Process::PATH_DELIMITER, remove_empty: true)

CrystalPath.expand_paths(paths)

paths
end

def self.default_path : String
ENV.fetch("CRYSTAL_LIBRARY_PATH", Crystal::Config.library_path)
default_paths.join(Process::PATH_DELIMITER)
end

class_getter paths : Array(String) do
default_path.split(Process::PATH_DELIMITER, remove_empty: true)
default_paths
end
end

Expand Down
57 changes: 48 additions & 9 deletions src/compiler/crystal/crystal_path.cr
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,61 @@ module Crystal

private DEFAULT_LIB_PATH = "lib"

def self.default_path
ENV["CRYSTAL_PATH"]? || begin
if Crystal::Config.path.blank?
DEFAULT_LIB_PATH
elsif Crystal::Config.path.split(Process::PATH_DELIMITER).includes?(DEFAULT_LIB_PATH)
Crystal::Config.path
def self.default_paths : Array(String)
if path = ENV["CRYSTAL_PATH"]?
path_array = path.split(Process::PATH_DELIMITER, remove_empty: true)
elsif path = Crystal::Config.path.presence
path_array = path.split(Process::PATH_DELIMITER, remove_empty: true)
unless path_array.includes?(DEFAULT_LIB_PATH)
path_array.unshift DEFAULT_LIB_PATH
end
else
path_array = [DEFAULT_LIB_PATH]
end

expand_paths(path_array)

path_array
end

def self.default_path : String
default_paths.join(Process::PATH_DELIMITER)
end

# Expand `$ORIGIN` in the paths to the directory where the compiler binary
# is located (at runtime).
# For install locations like
# `/path/prefix/bin/crystal` for the compiler
# `/path/prefix/share/crystal/src` for the standard library
# the path `$ORIGIN/../share/crystal/src` resolves to
# the standard library location.
# This generic path can be passed into the compiler via CRYSTAL_CONFIG_PATH
# to produce a portable binary that resolves the standard library path
# relative to the compiler location, independent of the absolute path.
def self.expand_paths(paths, origin)
paths.map! do |path|
if (chopped = path.lchop?("$ORIGIN")) && chopped[0].in?(::Path::SEPARATORS)
if origin.nil?
raise "Missing executable path to expand $ORIGIN path"
end
File.join(origin, chopped)
else
{DEFAULT_LIB_PATH, Crystal::Config.path}.join(Process::PATH_DELIMITER)
path
end
end
end

def self.expand_paths(paths)
origin = nil
if executable_path = Process.executable_path
origin = File.dirname(executable_path)
end
expand_paths(paths, origin)
end

property entries : Array(String)

def initialize(path = CrystalPath.default_path, codegen_target = Config.host_target)
@entries = path.split(Process::PATH_DELIMITER).reject &.empty?
def initialize(@entries : Array(String) = CrystalPath.default_paths, codegen_target = Config.host_target)
add_target_path(codegen_target)
end

Expand Down