Skip to content

Compiler confused about symbolic link in path to stdlib  #12969

Open
@straight-shoota

Description

@straight-shoota

The compiler cannot figure out that two paths point to the same file when they're symbolic links. It treats them as separate files and requires each path on its own. As a result, a file can be evaluated multiple times which can cause errors about re-opening enums or re-defining constants.

This is easy to reproduce by creating a symlink to a working checkout of this repository:

$ ln -s crystal ../crystal-symlink
$ cd ../crystal-symlink
$ make crystal
Using /usr/bin/llvm-config-14 [version= 14.0.0]
CRYSTAL_CONFIG_BUILD_COMMIT="4ed185ea6" CRYSTAL_CONFIG_PATH='$ORIGIN/../share/crystal/src' SOURCE_DATE_EPOCH="1673965574" CC="cc -fuse-ld=lld" CRYSTAL_CONFIG_LIBRARY_PATH='$ORIGIN/../lib/crystal' ./bin/crystal build -D strict_multi_assign -D preview_overload_order -Dwithout_interpreter  -o .build/crystal src/compiler/crystal.cr -D without_openssl -D without_zlib
Showing last frame. Use --error-trace for full trace.

In /home/johannes/src/crystal-lang/crystal/src/compiler/crystal/syntax/ast.cr:120:5

 120 | enum Keyword
       ^
Error: can't reopen enum and add more constants to it
make: *** [Makefile:190: .build/crystal] Fehler 1
$ pwd
/home/johannes/src/crystal-lang/crystal-symlink
$ bin/crystal env CRYSTAL_PATH
lib:/home/johannes/src/crystal-lang/crystal/src

The bin/crystal wrapper resolves symlinks in SCRIPT_PATH, thus CRYSTAL_PATH also points to the target directory instead of the link.
So the compiler looks up non-relative requires (require "foo") in the realpath (./crystal/src), and relative requires (require "./foo") in the symlink path (./crystal-symlink/src).

This is a practical problem when the repository is checked out in a path that has a symlink somewhere. For example, on FreeBSD ~ is /home/username, but /home is a symlink to /usr/home/. If you checkout the Crystal repository in your home path, the compiler won't build, and many specs will also fail.

The immediate confusion is caused by the wrapper script. It resolves the realpath of CRYSTAL_PATH which is derived from CWD, but does not resolve CWD itself and thus creates a discrepancy.
To fix this, the wrapper script could cd into CRYSTAL_ROOT (which is the realpath) before executing the compiler.

I'm also wondering if the compiler should be able to resolve symlinks for discovering whether a file was already required.
I don't see a clear use case for that, except for the bug in the wrapper script which we can fix on its own.
Usually this isn't a problem because you don't require the same source file from both a relative and a non-relative require. This only applies when the file path is reachable through CRYSTAL_PATH and a relative path. That typically only matches for stdlib/compiler sources.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions