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

[rc] Add llvm-tools-<maj> on windows as well #285

Open
wants to merge 7 commits into
base: rc
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion recipe/bld.bat
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ cmake -G "Ninja" ^
-DLLVM_UTILS_INSTALL_DIR=libexec\llvm ^
-DLLVM_BUILD_LLVM_C_DYLIB=ON ^
-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly ^
-DCMAKE_POLICY_DEFAULT_CMP0111=NEW ^
%SRC_DIR%/llvm
if %ERRORLEVEL% neq 0 exit 1

Expand Down
31 changes: 30 additions & 1 deletion recipe/install_llvm.bat
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,43 @@ if "%PKG_NAME%" == "libllvm-c%PKG_VERSION:~0,2%" (
REM only libLLVM-C
move .\temp_prefix\bin\LLVM-C.dll %LIBRARY_BIN%
move .\temp_prefix\lib\LLVM-C.lib %LIBRARY_LIB%
) else if "%PKG_NAME%" == "llvm-tools-%PKG_VERSION:~0,2%" (
cmake --install ./build --prefix=./temp_prefix
if %ERRORLEVEL% neq 0 exit 1

REM all the executables (not .dll's) in \bin with a version suffix,
REM except one binary that belongs to llvmdev
del .\temp_prefix\bin\llvm-config.exe

pushd .\temp_prefix\bin
for %%f in (*.exe) do (
echo %%~nf
copy "%%~nf.exe" %LIBRARY_BIN%\%%~nf-%PKG_VERSION:~0,2%.exe
)
popd
) else if "%PKG_NAME%" == "llvm-tools" (
cmake --install ./build --prefix=./temp_prefix
if %ERRORLEVEL% neq 0 exit 1

mkdir %LIBRARY_PREFIX%\share
REM all the executables (not .dll's) in \bin & everything in \share
move .\temp_prefix\bin\*.exe %LIBRARY_BIN%
move .\temp_prefix\share\* %LIBRARY_PREFIX%\share

REM create wrappers (can't do symlinks on windows by default,
REM needs to be compiled to be usable without extra ceremony)
pushd .\temp_prefix\bin
REM forwarder to call the versioned binary
copy %RECIPE_DIR%\win_forwarder.c .\win_forwarder.c
REM replace templated version number in code
sed -i "s/{{ majorversion }}/%PKG_VERSION:~0,2%/g" .\win_forwarder.c
REM the forwarder constructs the call based on its own filename,
REM so we only need to compile it once...
%CC% /MD .\win_forwarder.c
for %%f in (*.exe) do (
REM .. and then create copies for each binary
copy .\win_forwarder.exe %LIBRARY_BIN%\%%~nf.exe
)
popd
del %LIBRARY_BIN%\llvm-config.exe
) else (
REM llvmdev: everything else
Expand Down
57 changes: 31 additions & 26 deletions recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ source:
- patches/0001-pass-through-QEMU_LD_PREFIX-SDKROOT.patch

build:
number: 0
number: 1
merge_build_host: false

requirements:
Expand Down Expand Up @@ -152,9 +152,6 @@ outputs:
script: install_llvm.bat # [win]
build:
activate_in_script: true
# On Windows there are no symlinks and copying will create a new package
# that is 300MB+
skip: true # [win]
requirements:
build:
- {{ stdlib('c') }}
Expand All @@ -166,6 +163,7 @@ outputs:
host:
- libcxx {{ cxx_compiler_version }} # [osx]
- {{ pin_subpackage("libllvm" ~ major_ver, exact=True) }}
- libxml2 # [win]
- zlib
- zstd
run:
Expand All @@ -174,13 +172,11 @@ outputs:
- libcxx >={{ cxx_compiler_version }} # [osx]
test:
commands:
- $PREFIX/bin/llc-{{ major_ver }} -version # [not win]
# The test for windows is split into two lines instead of having it in one line
# like its unix variant because of a YAML parsing issue.
- if not exist "%LIBRARY_BIN%"\\llc-{{ major_ver }}.exe exit 1 # [win]
- llc-{{ major_ver }} -version # [win]
- test ! -f $PREFIX/bin/llvm-config-{{ major_ver }} # [not win]
- if exist "%LIBRARY_BIN%"\\llvm-config-{{ major_ver }}.exe exit 1 # [win]
- $PREFIX/bin/llc-{{ major_ver }} -version # [unix]
- "%LIBRARY_BIN%\\llc-{{ major_ver }}.exe -version" # [win]
# llvm-config belongs into llvmdev
- test ! -f $PREFIX/bin/llvm-config-{{ major_ver }} # [unix]
- if exist %LIBRARY_BIN%\llvm-config-{{ major_ver }}.exe exit 1 # [win]

# Contains LLVM tools
- name: llvm-tools
Expand All @@ -196,30 +192,39 @@ outputs:
- ninja
- python >=3
- libcxx {{ cxx_compiler_version }} # [osx]
# for preparing win_forwarder.c
- m2-sed # [win]
host:
- {{ pin_subpackage("libllvm" ~ major_ver, exact=True) }}
- {{ pin_subpackage("llvm-tools-" ~ major_ver, exact=True) }} # [unix]
# on unix, there's only symlinks
- libxml2 # [win]
- zlib # [win]
- zstd # [win]
- {{ pin_subpackage("llvm-tools-" ~ major_ver, exact=True) }}
run:
- {{ pin_subpackage("libllvm" ~ major_ver, exact=True) }}
- {{ pin_subpackage("llvm-tools-" ~ major_ver, exact=True) }} # [unix]
- {{ pin_subpackage("llvm-tools-" ~ major_ver, exact=True) }}
run_constrained:
- llvm {{ version }}
- llvmdev {{ version }}
- clang {{ version }}
- clang-tools {{ version }}
test:
commands:
- $PREFIX/bin/llc -version # [not win]
# The test for windows is split into two lines instead of having it in one line
# like its unix variant because of a YAML parsing issue.
- if not exist "%LIBRARY_BIN%"\\llc.exe exit 1 # [win]
- llc -version # [win]
- test ! -f $PREFIX/bin/llvm-config # [not win]
- if exist "%LIBRARY_BIN%"\\llvm-config.exe exit 1 # [win]
- $PREFIX/bin/llc -version # [unix]
- "%LIBRARY_BIN%\\llc.exe -version" # [win]
# test different styles to call forwarder
- call llc.exe -version # [win]
- call llc -version # [win]
- llc.exe -version # [win]
- llc -version # [win]
# test correct return value for failure (passing directory instead of file)
- call llc.exe . & if ERRORLEVEL ==1 (exit 0) else (exit 1) # [win]
- call llc . & if ERRORLEVEL ==1 (exit 0) else (exit 1) # [win]
- llc.exe . & if ERRORLEVEL ==1 (exit 0) else (exit 1) # [win]
- llc . & if ERRORLEVEL ==1 (exit 0) else (exit 1) # [win]
# do not use %ERRORLEVEL% or !ERRORLEVEL!, but ERRORLEVEL, c.f.
# https://devblogs.microsoft.com/oldnewthing/20080926-00/?p=20743;
# while `if ... NEQ 1 exit 1` would be nicer, NEQ is incompatible with
# using bare ERRORLEVEL, see https://stackoverflow.com/a/74148543

# llvm-config belongs into llvmdev
- test ! -f $PREFIX/bin/llvm-config # [unix]
- if exist %LIBRARY_BIN%\llvm-config.exe exit 1 # [win]

# Contains LLVM-C shared library
- name: libllvm-c{{ major_ver }}
Expand Down
103 changes: 103 additions & 0 deletions recipe/win_forwarder.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// adapted from example in MSFT docs, see
// https://learn.microsoft.com/en-us/windows/win32/procthread/creating-processes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the license on this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MIT, see here, which covers the code in doc source

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any other concerns besides the license?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gentle ping on this @isuruf

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another ping here... I'm aware this is a trade-off between consistency across platforms and extra complexity (though we hopefully shouldn't have to touch it going forward; might also consider breaking this out into something reusable wherever we have versioned binaries). I don't have a clear answer what is better, so I'd appreciate your input @isuruf.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use symlink-exe-substitute-feedstock ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like that's built to symlink an exe into another folder, not to a differently-named file in the same folder (it also doesn't use the relative path logic). That said, the approach could probably be extended to name the target symlink, and it's cleaner because it doesn't duplicate the 12kb per link (at the cost of introducing an extra run-dependency).

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <tchar.h>

// the idea here is as follows; this wrapper will be compiled and renamed into
// a file corresponding to the versioned binaries created in install_llvm.bat.
// For example, we'll have llc.exe and llc-19.exe, both under %LIBRARY_BIN%.
// To avoid security holes, we ensure that the wrapper can only call binaries
// in %LIBRARY_BIN%, otherwise we fail. For example:
// * user calls `llc -version`; argv[0] == "llc", argv[1] == "-version"
// * determine path of calling binary, i.e. %LIBRARY_BIN%\llc.exe
// * construct versioned path, i.e. %LIBRARY_BIN%\llc-19.exe
// * collect all other arguments & quote them, e.g. `"argv[1]" "argv[2]" ...`
// * invoke `%LIBRARY_BIN%\llc-19.exe "argv[1]" "argv[2]" ...`
// * collect return value from inner call and return the same from wrapper

int _tmain( int argc, TCHAR *argv[] )
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
DWORD error_code;

ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );

// reconstruct commandline call we received, pointing to versioned binary;
// 32767 == maximum length for lpCommandLine argument
char forwarded[32767];
// initialize buffer with dummy so that strlen below is not UB
strcat(forwarded, "xxx");

// argv[0] is the name of the calling function, which might be a relative path;
// for security reasons, get the absolute path of the binary we're calling from
// with help from the MIT-licensed https://github.com/gpakosz/whereami
void* addr = _ReturnAddress();
HMODULE hm = NULL;
// non-zero return means success
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCSTR) addr, &hm))
GetModuleFileNameA(hm, forwarded, sizeof(forwarded));

unsigned int len = strlen(forwarded);
if (len < 4) {
// realistically, minimum len(r"C:\a") == 4; dummy value has length 3
printf( "Could not determine location of calling binary!\n" );
return GetLastError();
}
// chop off ".exe" (if the binary we're in was invoked like that)
if (strcmp(&forwarded[len-4], ".exe") == 0)
// set null-terminator to finish the string early
forwarded[len-4] = '\0';
// point to versioned binary
strcat(forwarded, "-{{ majorversion }}.exe");
// rest stays the same, but wrap everything in quotes, because
// the contents of argv[i] get stripped of those, which fails
// if there's any argument that relies on atomicity, e.g. paths
// with spaces in them (c.f. "C:\Program Files\...")
for (int i = 1; i < argc; i++) {
strcat(forwarded, " \"");
strcat(forwarded, argv[i]);
strcat(forwarded, "\"");
}

// Start the child process.
if( !CreateProcess( NULL, // No module name (use command line)
(char*)&forwarded, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi ) ) // Pointer to PROCESS_INFORMATION structure
{
printf( "Forwarding call to versioned binary failed:\n" );
printf( "%s", forwarded );
return GetLastError();
}

// Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );

// Needs be called before the thread terminates, even though the value
// only gets populated after the thread terminates.
// If GetExitCodeProcess succeeds, it will correctly set &error_code to
// the result of the inner call, but will return a non-zero value itself.
// If it fails, use the last available error. For details see
// https://learn.microsoft.com/en-gb/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
if( !GetExitCodeProcess( pi.hProcess, &error_code ) )
error_code = GetLastError();

// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );

return error_code;
}
Loading