Skip to content

Commit

Permalink
Windows: add wrapper .exe to work around lack of RPATH
Browse files Browse the repository at this point in the history
Windows has no concept of `RPATH`, which makes shipping binaries with
relatively-located dependencies quite challenging.  Despite
investigating many potential avenues of `RPATH` emulation, (including
but not limited to Application Manifests, PE file patching, and bundling
the true Julia executable as a resource inside of a launcher) the most
reliable (And least breaking for external workflows) was found to be to
create a launcher executable that invokes the true Julia executable from
within the `libexec` directory.
  • Loading branch information
staticfloat committed Apr 28, 2020
1 parent 5d32089 commit f383e78
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 0 deletions.
73 changes: 73 additions & 0 deletions contrib/windows/exe_path_wrapper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <shlwapi.h>
#define ENVVAR_MAXLEN 32760

/* PATH_ENTRIES is our simulated RPATH, usually of the form 'TEXT("../path1"), TEXT("../path2"), ...' */
#ifndef PATH_ENTRIES
#define PATH_ENTRIES TEXT("")
#endif

LPWSTR pathEntries[] = {
PATH_ENTRIES
};

/* JULIA_EXE_PATH is the relative path to julia.exe */
#ifndef JULIA_EXE_PATH
#define JULIA_EXE_PATH "../libexec/julia.exe"
#endif

int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) {
// Determine absolute path to true julia.exe sitting in `libexec/`
WCHAR currFileDir[MAX_PATH];
WCHAR juliaPath[MAX_PATH];
GetModuleFileName(NULL, currFileDir, MAX_PATH);
PathRemoveFileSpec(currFileDir);
PathCombine(juliaPath, currFileDir, TEXT(JULIA_EXE_PATH));

// On windows, we simulate RPATH by pushing onto PATH
LPWSTR pathVal = (LPWSTR) malloc(ENVVAR_MAXLEN*sizeof(WCHAR));
DWORD dwRet = GetEnvironmentVariable(TEXT("PATH"), pathVal, ENVVAR_MAXLEN);
DWORD numPathEntries = sizeof(pathEntries)/sizeof(pathEntries[0]);
if (dwRet == 0) {
// If we cannot get PATH, then our job is easy!
pathVal[0] = '\0';
} else {
// Otherwise, we append, if we have enough space to:
DWORD currFileDirLen = wcslen(currFileDir);
DWORD totalPathLen = dwRet + 1 + currFileDirLen;
for (int env_idx=0; env_idx<numPathEntries; ++env_idx) {
totalPathLen += 1 + currFileDirLen + 1 + wcslen(pathEntries[env_idx]);
}
if (ENVVAR_MAXLEN < totalPathLen ) {
fprintf(stderr, "ERROR: Cannot append entries to PATH: not enough space in environment block. Reduce size of PATH!");
exit(1);
}
lstrcat(pathVal, TEXT(";"));
}
// We always add the current directory (e.g. `bin/`) to PATH so that we can find e.g. libjulia.dll
lstrcat(pathVal, currFileDir);

// For each entry in PATH_ENTRIES, tack it on to the end, relative to the current directory:
for (int env_idx=0; env_idx<numPathEntries; ++env_idx) {
lstrcat(pathVal, TEXT(";"));
lstrcat(pathVal, currFileDir);
lstrcat(pathVal, TEXT("\\"));
lstrcat(pathVal, pathEntries[env_idx]);
}
SetEnvironmentVariable(TEXT("PATH"), pathVal);
free(pathVal);

STARTUPINFO info;
PROCESS_INFORMATION processInfo;
DWORD exit_code = 1;
GetStartupInfo(&info);
if (CreateProcess(juliaPath, GetCommandLine(), NULL, NULL, TRUE, 0, NULL, NULL, &info, &processInfo)) {
WaitForSingleObject(processInfo.hProcess, INFINITE);
GetExitCodeProcess(processInfo.hProcess, &exit_code);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
return exit_code;
}
28 changes: 28 additions & 0 deletions ui/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ $(BUILDDIR)/%.dbg.obj: $(SRCDIR)/%.c $(HEADERS)
@$(call PRINT_CC, $(CC) $(CPPFLAGS) $(CFLAGS) $(DEBUGFLAGS) -c $< -o $@)

ifeq ($(OS),WINNT)
JLDFLAGS += -municode

ifneq ($(USEMSVC), 1)
$(BUILDDIR)/julia_res.o: $(JULIAHOME)/contrib/windows/julia.rc $(JULIAHOME)/VERSION
JLVER=`cat $(JULIAHOME)/VERSION` && \
Expand Down Expand Up @@ -82,10 +84,36 @@ else
CXXLD := $(LD)
endif

# Add `.exe` wrapper generation to properly setup `PATH` on windows to simulate `RPATH`
ifeq ($(OS),WINNT)
define rel_path_entry
TEXT(\"$$(echo $(call rel_path,$(build_libexecdir),$(1)) | sed -e 's_/_\\\\_g')\")
endef
CPPFLAGS_PATH = -DPATH_ENTRIES="$(call rel_path_entry,$(CSL_SHLIBDIR)), $(call rel_path_entry,$(LIBLLVM_SHLIBDIR))"
define julia_exe_path
-DJULIA_EXE_PATH="\"$$(echo '$(libexecdir_rel)$(PATHSEP)$(1)' | sed -e 's_\\_\\\\_g')\""
endef
define gen_wrapper_exe
-mv $(build_bindir)/$(1) $(build_libexecdir)/$(1)
@$(call PRINT_CC, $(CC) $(CPPFLAGS) $(CFLAGS) $(SHIPFLAGS) $(call julia_exe_path,$(1)) $(CPPFLAGS_PATH) $(JULIAHOME)/contrib/windows/exe_path_wrapper.c -o $(build_bindir)/$(1) $(JLDFLAGS) -lshlwapi)
endef

$(build_bindir)/julia$(EXE): $(JULIAHOME)/contrib/windows/exe_path_wrapper.c
$(build_bindir)/julia-debug$(EXE): $(JULIAHOME)/contrib/windows/exe_path_wrapper.c
endif


$(build_bindir)/julia$(EXE): $(OBJS)
@$(call PRINT_LINK, $(CXXLD) $(CXXFLAGS) $(CXXLDFLAGS) $(LINK_FLAGS) $(SHIPFLAGS) $(OBJS) -o $@ -L$(build_private_libdir) -L$(build_libdir) -L$(build_shlibdir) -ljulia $(JLDFLAGS) $(CXXLDFLAGS))
ifeq ($(OS),WINNT)
@$(call gen_wrapper_exe,julia$(EXE))
endif

$(build_bindir)/julia-debug$(EXE): $(DOBJS)
@$(call PRINT_LINK, $(CXXLD) $(CXXFLAGS) $(CXXLDFLAGS) $(LINK_FLAGS) $(DEBUGFLAGS) $(DOBJS) -o $@ -L$(build_private_libdir) -L$(build_libdir) -L$(build_shlibdir) -ljulia-debug $(JLDFLAGS) $(CXXLDFLAGS))
ifeq ($(OS),WINNT)
@$(call gen_wrapper_exe,julia-debug$(EXE))
endif

clean: | $(CLEAN_TARGETS)
rm -f *.o *.dbg.obj
Expand Down

0 comments on commit f383e78

Please sign in to comment.