Skip to content

Commit

Permalink
Make Makefile work on MSYS2 (#15102)
Browse files Browse the repository at this point in the history
* Uses `llvm-config` instead of `llvm_VERSION` for the `x86_64-windows-gnu` target. Also invokes the `find-llvm-config` script using `sh` explicitly and ensures the script returns a Windows path. (This pretty much means a MinGW-w64-based compiler will require the entire MSYS2 environment to work.)
* Increases the stack size from the default 2 MiB to 8 MiB, consistent with builds using the MSVC toolchain.
* `Makefile` and the `bin/crystal` script now recognize `.build/crystal.exe` as the local compiler under MSYS2.
* If `.build/crystal.exe` already exists and we are on Windows, `Makefile` now builds a temporary executable first, just like in `Makefile.win`.
* Ensures `Makefile` doesn't interpret backslashes in `llvm-config.exe`'s path as escape sequences.
* Adds a separate `install_dlls` target for installing the compiler's dependent DLLs to the given prefix's `bin` directory. This is only needed if the installation is to be distributed outside MSYS2.
* Detects the presence of `lld` which can be installed using the `mingw-w64-ucrt-x86_64-lld` package.

With this patch, cross-compiling from MSVC-based Crystal will no longer work until #15091 is merged. Instead it can be done from Linux, including WSL, assuming the host and target LLVM versions are identical (right now MSYS2 has 18.1.8):

```sh
# on Linux
make clean crystal && make -B target=x86_64-windows-gnu

# on MSYS2's UCRT64 environment
pacman -Sy \
  mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-pkgconf \
  mingw-w64-ucrt-x86_64-gc mingw-w64-ucrt-x86_64-pcre2 mingw-w64-ucrt-x86_64-libiconv \
  mingw-w64-ucrt-x86_64-zlib mingw-w64-ucrt-x86_64-llvm
cc .build/crystal.obj -o .build/crystal.exe \
  $(pkg-config bdw-gc libpcre2-8 iconv zlib --libs) \
  $(llvm-config --libs --system-libs --ldflags) \
  -lDbgHelp -lole32 -lWS2_32 -Wl,--stack,0x800000
```
  • Loading branch information
HertzDevil authored Oct 21, 2024
1 parent 8f49ab9 commit b10f171
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 18 deletions.
44 changes: 32 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ CRYSTAL_CONFIG_BUILD_COMMIT ?= $(shell git rev-parse --short HEAD 2> /dev/null)
CRYSTAL_CONFIG_PATH := '$$ORIGIN/../share/crystal/src'
CRYSTAL_VERSION ?= $(shell cat src/VERSION)
SOURCE_DATE_EPOCH ?= $(shell (cat src/SOURCE_DATE_EPOCH || (git show -s --format=%ct HEAD || stat -c "%Y" Makefile || stat -f "%m" Makefile)) 2> /dev/null)
ifeq ($(shell command -v ld.lld >/dev/null && uname -s),Linux)
check_lld := command -v ld.lld >/dev/null && case "$$(uname -s)" in MINGW32*|MINGW64*|Linux) echo 1;; esac
ifeq ($(shell $(check_lld)),1)
EXPORT_CC ?= CC="$(CC) -fuse-ld=lld"
endif
EXPORTS := \
Expand All @@ -60,11 +61,20 @@ EXPORTS_BUILD := \
CRYSTAL_CONFIG_LIBRARY_PATH=$(CRYSTAL_CONFIG_LIBRARY_PATH)
SHELL = sh
LLVM_CONFIG := $(shell src/llvm/ext/find-llvm-config)
LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell $(LLVM_CONFIG) --version 2> /dev/null))
LLVM_VERSION := $(if $(LLVM_CONFIG),$(shell "$(LLVM_CONFIG)" --version 2> /dev/null))
LLVM_EXT_DIR = src/llvm/ext
LLVM_EXT_OBJ = $(LLVM_EXT_DIR)/llvm_ext.o
CXXFLAGS += $(if $(debug),-g -O0)

# MSYS2 support (native Windows should use `Makefile.win` instead)
ifeq ($(OS),Windows_NT)
CRYSTAL_BIN := crystal.exe
WINDOWS := 1
else
CRYSTAL_BIN := crystal
WINDOWS :=
endif

DESTDIR ?=
PREFIX ?= /usr/local
BINDIR ?= $(DESTDIR)$(PREFIX)/bin
Expand All @@ -74,9 +84,9 @@ DATADIR ?= $(DESTDIR)$(PREFIX)/share/crystal
INSTALL ?= /usr/bin/install

ifeq ($(or $(TERM),$(TERM),dumb),dumb)
colorize = $(shell printf >&2 "$1")
colorize = $(shell printf "%s" "$1" >&2)
else
colorize = $(shell printf >&2 "\033[33m$1\033[0m\n")
colorize = $(shell printf "\033[33m%s\033[0m\n" "$1" >&2)
endif

DEPS = $(LLVM_EXT_OBJ)
Expand Down Expand Up @@ -119,7 +129,7 @@ interpreter_spec: $(O)/interpreter_spec ## Run interpreter specs

.PHONY: smoke_test
smoke_test: ## Build specs as a smoke test
smoke_test: $(O)/std_spec $(O)/compiler_spec $(O)/crystal
smoke_test: $(O)/std_spec $(O)/compiler_spec $(O)/$(CRYSTAL_BIN)

.PHONY: all_spec
all_spec: $(O)/all_spec ## Run all specs (note: this builds a huge program; `test` recipe builds individual binaries and is recommended for reduced resource usage)
Expand All @@ -136,7 +146,7 @@ docs: ## Generate standard library documentation
cp -av doc/ docs/

.PHONY: crystal
crystal: $(O)/crystal ## Build the compiler
crystal: $(O)/$(CRYSTAL_BIN) ## Build the compiler

.PHONY: deps llvm_ext
deps: $(DEPS) ## Build dependencies
Expand All @@ -151,9 +161,9 @@ generate_data: ## Run generator scripts for Unicode, SSL config, ...
$(MAKE) -B -f scripts/generate_data.mk

.PHONY: install
install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR
install: $(O)/$(CRYSTAL_BIN) man/crystal.1.gz ## Install the compiler at DESTDIR
$(INSTALL) -d -m 0755 "$(BINDIR)/"
$(INSTALL) -m 0755 "$(O)/crystal" "$(BINDIR)/crystal"
$(INSTALL) -m 0755 "$(O)/$(CRYSTAL_BIN)" "$(BINDIR)/$(CRYSTAL_BIN)"

$(INSTALL) -d -m 0755 $(DATADIR)
cp -av src "$(DATADIR)/src"
Expand All @@ -171,9 +181,16 @@ install: $(O)/crystal man/crystal.1.gz ## Install the compiler at DESTDIR
$(INSTALL) -d -m 0755 "$(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/"
$(INSTALL) -m 644 etc/completion.fish "$(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/crystal.fish"

ifeq ($(WINDOWS),1)
.PHONY: install_dlls
install_dlls: $(O)/$(CRYSTAL_BIN) ## Install the compiler's dependent DLLs at DESTDIR (Windows only)
$(INSTALL) -d -m 0755 "$(BINDIR)/"
@ldd $(O)/$(CRYSTAL_BIN) | grep -iv ' => /c/windows/system32' | sed 's/.* => //; s/ (.*//' | xargs -t -i $(INSTALL) -m 0755 '{}' "$(BINDIR)/"
endif

.PHONY: uninstall
uninstall: ## Uninstall the compiler from DESTDIR
rm -f "$(BINDIR)/crystal"
rm -f "$(BINDIR)/$(CRYSTAL_BIN)"

rm -rf "$(DATADIR)/src"

Expand Down Expand Up @@ -210,7 +227,7 @@ $(O)/compiler_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
@mkdir -p $(O)
$(EXPORT_CC) $(EXPORTS) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler_spec.cr --release

$(O)/primitives_spec: $(O)/crystal $(DEPS) $(SOURCES) $(SPEC_SOURCES)
$(O)/primitives_spec: $(O)/$(CRYSTAL_BIN) $(DEPS) $(SOURCES) $(SPEC_SOURCES)
@mkdir -p $(O)
$(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/primitives_spec.cr

Expand All @@ -219,12 +236,15 @@ $(O)/interpreter_spec: $(DEPS) $(SOURCES) $(SPEC_SOURCES)
@mkdir -p $(O)
$(EXPORT_CC) ./bin/crystal build $(FLAGS) $(SPEC_WARNINGS_OFF) -o $@ spec/compiler/interpreter_spec.cr

$(O)/crystal: $(DEPS) $(SOURCES)
$(O)/$(CRYSTAL_BIN): $(DEPS) $(SOURCES)
$(call check_llvm_config)
@mkdir -p $(O)
@# NOTE: USE_PCRE1 is only used for testing compatibility with legacy environments that don't provide libpcre2.
@# Newly built compilers should never be distributed with libpcre to ensure syntax consistency.
$(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $@ src/compiler/crystal.cr -D without_openssl -D without_zlib $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2)
$(EXPORTS) $(EXPORTS_BUILD) ./bin/crystal build $(FLAGS) -o $(if $(WINDOWS),$(O)/crystal-next.exe,$@) src/compiler/crystal.cr -D without_openssl -D without_zlib $(if $(USE_PCRE1),-D use_pcre,-D use_pcre2)
@# NOTE: on MSYS2 it is not possible to overwrite a running program, so the compiler must be first built with
@# a different filename and then moved to the final destination.
$(if $(WINDOWS),mv $(O)/crystal-next.exe $@)

$(LLVM_EXT_OBJ): $(LLVM_EXT_DIR)/llvm_ext.cc
$(call check_llvm_config)
Expand Down
15 changes: 12 additions & 3 deletions bin/crystal
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,18 @@ fi
# with symlinks resolved as well (see https://github.com/crystal-lang/crystal/issues/12969).
cd "$(realpath "$(pwd)")"

if [ -x "$CRYSTAL_DIR/crystal" ]; then
__warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/crystal"
exec "$CRYSTAL_DIR/crystal" "$@"
case "$(uname -s)" in
CYGWIN*|MSYS_NT*|MINGW32_NT*|MINGW64_NT*)
CRYSTAL_BIN="crystal.exe"
;;
*)
CRYSTAL_BIN="crystal"
;;
esac

if [ -x "$CRYSTAL_DIR/${CRYSTAL_BIN}" ]; then
__warning_msg "Using compiled compiler at ${CRYSTAL_DIR#"$PWD/"}/${CRYSTAL_BIN}"
exec "$CRYSTAL_DIR/${CRYSTAL_BIN}" "$@"
elif !($PARENT_CRYSTAL_EXISTS); then
__error_msg 'You need to have a crystal executable in your path! or set CRYSTAL env variable'
exit 1
Expand Down
1 change: 1 addition & 0 deletions src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ module Crystal
{DEFAULT_LINKER, %(#{DEFAULT_LINKER} "${@}" -o #{Process.quote_posix(output_filename)} #{link_flags} #{program.lib_flags(@cross_compile)}), object_names}
elsif program.has_flag?("win32") && program.has_flag?("gnu")
link_flags = @link_flags || ""
link_flags += " -Wl,--stack,0x800000"
lib_flags = program.lib_flags(@cross_compile)
lib_flags = expand_lib_flags(lib_flags) if expand
cmd = %(#{DEFAULT_LINKER} #{Process.quote_windows(object_names)} -o #{Process.quote_windows(output_filename)} #{link_flags} #{lib_flags})
Expand Down
9 changes: 8 additions & 1 deletion src/llvm/ext/find-llvm-config
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ if ! LLVM_CONFIG=$(command -v "$LLVM_CONFIG"); then
fi

if [ "$LLVM_CONFIG" ]; then
printf %s "$LLVM_CONFIG"
case "$(uname -s)" in
MINGW32_NT*|MINGW64_NT*)
printf "%s" "$(cygpath -w "$LLVM_CONFIG")"
;;
*)
printf "%s" "$LLVM_CONFIG"
;;
esac
else
printf "Error: Could not find location of llvm-config. Please specify path in environment variable LLVM_CONFIG.\n" >&2
printf "Supported LLVM versions: $(cat "$(dirname $0)/llvm-versions.txt" | sed 's/\.0//g')\n" >&2
Expand Down
4 changes: 2 additions & 2 deletions src/llvm/lib_llvm.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% begin %}
{% if flag?(:win32) && !flag?(:static) %}
{% if flag?(:msvc) && !flag?(:static) %}
{% config = nil %}
{% for dir in Crystal::LIBRARY_PATH.split(Crystal::System::Process::HOST_PATH_DELIMITER) %}
{% config ||= read_file?("#{dir.id}/llvm_VERSION") %}
Expand All @@ -21,7 +21,7 @@
lib LibLLVM
end
{% else %}
{% llvm_config = env("LLVM_CONFIG") || `#{__DIR__}/ext/find-llvm-config`.stringify %}
{% llvm_config = env("LLVM_CONFIG") || `sh #{__DIR__}/ext/find-llvm-config`.stringify %}
{% llvm_version = `#{llvm_config.id} --version`.stringify %}
{% llvm_targets = env("LLVM_TARGETS") || `#{llvm_config.id} --targets-built`.stringify %}
{% llvm_ldflags = "`#{llvm_config.id} --libs --system-libs --ldflags#{" --link-static".id if flag?(:static)}#{" 2> /dev/null".id unless flag?(:win32)}`" %}
Expand Down

0 comments on commit b10f171

Please sign in to comment.