-
Notifications
You must be signed in to change notification settings - Fork 348
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
C stubs for native Windows support #3242
Conversation
@@ -3099,12 +3102,26 @@ EOF | |||
$as_echo "$OCAML_OS_TYPE" >&6; } | |||
|
|||
|
|||
if test "${OCAML_OS_TYPE}" = "Win32" ; then | |||
if test "${OCAML_OS_TYPE}" = "Win32"; then : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, is this :
at the end for portability on Windows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's autoconf, not me 😄 That's coming from AS_IF
(I try to follow m4sh coding standards, although m4sh, coding and standards I find to be co-oxymoronic terms!). I think it's a cheap way of ensuring that you never get an empty then
branch, as that would be a syntax error. There's similar things in the Makefile
s of the OCaml testsuite, IIRC.
a9d9e19
to
b2ef05c
Compare
Great work! I'll review ASAP. Thanks! |
I've revised the Console stubs in the last few days (it's all pushed, so as here!). It should now be the case that the Unix and Windows 10 behaviour for the console status line is identical - so it does still use unflushed |
264de8d
to
33ec40f
Compare
I had made a fundamental and stupid mistake with the console function refactoring - |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this huge work.
Apart from a few separate nitpicking comments, the only part I am not totally convinced about is the UTF8 support, which adds quite a lot of complexity while the textual fallback was already working OK. In comparison, the color support is complex too, but more straightforward, and adds much more to the UI.
For the other parts, the W32 support for waitpid
(and consequently, full parallelism) is really awesome, putenv
is really neat, and better analysis for the os
, os-version
variables is great. Thanks!
src/core/opamStd.ml
Outdated
@@ -500,7 +500,7 @@ module OpamString = struct | |||
|
|||
end | |||
|
|||
|
|||
let is_windows = Sys.os_type = "Win32" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As was pointed to me earlier, since we now require 4.01.0, we can equivalently (but more cleanly) use Sys.win32
. See the (conflicting — sorry) patch 04bfd5d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true - good opportunity to change it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I'd already resolved the conflict!)
output_string str; | ||
in | ||
List.iter print_line table | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made some fixes to print_table
in the meantime, did you make sure to port them here too ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will double-check that's the case, but I think so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely OK (I diff'd the two code blocks on the last rebase to be sure)
src/core/opamConsole.ml
Outdated
@@ -186,7 +190,12 @@ let status_line fmt = | |||
(fun s -> if s <> !last_status then (last_status := s; print_endline s)) | |||
fmt | |||
else | |||
Printf.ksprintf print_string ("\r\027[K" ^^ fmt ^^ "%!\r\027[K") | |||
let print_string s = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am nitpicking ;)
but according to /.ocp-indent
, which has strict_else=auto
, this shouldn't be indented.
src/solver/opamActionGraph.ml
Outdated
| `Remove _ -> | ||
OpamConsole.utf8_symbol OpamConsole.Symbols.circled_division_slash "" | ||
utf8_symbol Symbols.circled_division_slash | ||
~alternates:[Symbols.greek_small_letter_lambda] "X" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have a fallback to more explicit strings (just above) in case UTF8 is not available. Are the single-letter fallbacks really needed ?
I figure the intent is to allow partial fallback if only some of the symbols are not available ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed - the alternates list is based on looking at the console fonts in previous versions of Windows and then having the single letter for complete failure. FWIW it was only for completeness - Consolas has something useful on all support versions of Windows.
@@ -17,6 +17,8 @@ | |||
(flags (:standard (:include ../ocaml-flags-standard.sexp) (:include ../ocaml-context-flags.sexp))) | |||
(libraries (opam-client)))) | |||
|
|||
(include opam-putenv.inc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really an issue, but we should be aware that we lose on Windows the property that opam can be used from a single installed binary (well, given that all its dependencies are there on course)
In any case, for Windows, we'll need some kind of installer, so that property isn't as valuable that it is on Linux. And it's in any case not a requirement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not completely lost - it's just that the user would otherwise have to ensure that they are using the correct opam.exe
. You really don't want to see the gymnastics required to eliminate opam-putenv.exe
completely 😉
@@ -0,0 +1,130 @@ | |||
(**************************************************************************) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this (and other added modules) could you also add a line in /doc/index.html
? Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
This is rebased and review comments addressed. Notes:
For the UTF-8 part: I'd anticipate that mucking around with console fonts on Unix is less common than on Windows. The problem with the UTF8 bit is that it assumes that either all the characters are there, or none of them - the Windows fallback otherwise is just to display a square, which is pretty hideous for things like the line-art. It's not crucial, but I think converting it to have both alternate code-points which may have glyphs and being able to use ASCII for specific missing chars only is better. I'll obviously keep on eye on new glyphs which appear in Consolas, but we can't easily legislate for other console fonts. |
I've checked that this still builds on my machine, but the CI needs to run too. |
Right, fingers crossed that should pass on macOS as well. Since Dune beta18, the |
Sys.win32 was added in OCaml 4.01.0, so it's available on all supported versions.
Unix.getpid doesn't return the actual Windows Process ID (see http://caml.inria.fr/mantis/view.php?id=4034). What's worse is that it can return the same process ID for two separate running instances, which creates problems for OPAM's logging code which, reasonably, assumes unique process IDs. Patch calls the Windows API function GetCurrentProcessID. This introduces C stubs to opam for the first time, but the C stubs are included in a library which is only generated on Windows. Signed-off-by: David Allsopp <david.allsopp@metastack.com>
OpamConsole uses OpamStd, preventing OpamStd.Config.env from printing coloured warning messages. This little hack breaks the dependency between the two, allowing OpamConsole.warning to be passed to OpamStd after load.
OpamConsole.msg, etc. already flush and don't support %!.
Prior to Windows 10 1511, the Windows Console is controlled using API functions, rather than escape sequences embedded in the text. This commit introduces three changes to faciliate emulation of VT100 sequences on the Windows Console: - All output to the Console containing coloured text must go through one of OpamConsole's functions (in particular, print_string, Printf.printf, output_string stdout/stderr must not be used). Using ksprintf instead of printf changes the type of functions used with %a. - OpamStd.Format.print_table is moved to OpamConsole.print_table, since it must now use OpamConsole output functions. - OpamConsole.carriage_delete replaces manually writing [K control sequence. This is isn't strictly necessary, but it's cleaner than having hard-coded control sequences. Signed-off-by: David Allsopp <david.allsopp@metastack.com>
%! doesn't work with ksprintf - fix the functions to call flush where appropriate.
The translation of VT100 sequences to Windows API calls for the Console mean that tricks relying on channel buffering no longer work (indeed, they can't be implemented without a change in OCaml). This necessitates a change in the way OpamConsole.status_line works, as it leaves the a carriage_delete operation pending on the channel such that flushing the channel will erase the status line. OpamConsole.clear_status must now be called when OpamConsole.status_line is no longer needed on-screen. It's not necessary to pair calls to status_line and clear_status. Signed-off-by: David Allsopp <david.allsopp@metastack.com>
Prior to Windows 10 1511, the Windows Console didn't natively support ANSI escape sequences, but it does include API functions providing most of the required support. In order to maintain the option of compiling with MSVC, a custom wrapper is implemented for the small number of required escape sequences, rather than using Cygwin or MSYS's ANSI layer. Particular care has been taken to ensure that OpamConsole.log and OpamConsole.slog behave in the same way; specifically this means that strings logged should never include ANSI escape sequences because these cannot be translated. Signed-off-by: David Allsopp <david.allsopp@metastack.com>
%! doesn't work with ksprintf - fix the functions to call flush where appropriate.
The with_process_in auxiliary in OpamSys is Unix-only. However, it's used for processing uname doesn't apply on Windows. Add an exception to guard against accidentally calling with_process_in on Windows. Neater alternatives for uname -s and uname -m added. Signed-off-by: David Allsopp <david.allsopp@metastack.com>
Unix.wait (and its equivalent Unix.waitpid [] -1) isn't available under Windows. Added a new stub OPAMW_waitpids which instead explicitly waits for one of the given processes to terminate using WaitForMultipleObjects. Signed-off-by: David Allsopp <david.allsopp@metastack.com>
Only REG_SZ implemented (for now) Signed-off-by: David Allsopp <david.allsopp@metastack.com>
Move handling of UTF-8 to OpamConsole, instead of having hard-coded UTF-8 sequences arbitrarily in code. Using Uchar from the stdlib requires extlib-compat rather than extlib (UChar.cmi mustn't be compiled on macOS or Windows). Signed-off-by: David Allsopp <david.allsopp@metastack.com>
The Windows Console Host supports UTF-8 as long as a TrueType font has been selected (two are installed by default). Console logic updated to detect this font support and utilise it, if UTF-8 support has been enabled (either by the user, or by OCaml 4.06.0+). At present, the Console does not support Unicode surrogate pairs, so sadly there will be no Camel symbol for OPAM-on-Windows for the foreseeable future :'( (additionally, the only common font containing it is Segoe UI Symbol which is not usable as a Console font). Although Windows has two suitable fonts (Lucida Console and Consolas) containing Unicode glyphs, neither of these fonts contain the glyphs used by OPAM's solver. Unsurprisingly, Windows font mapping for symbols is also vastly inferior compared with Linux, so instead of requiring the user to find a suitable console font, OPAM detects the glyphs supported by the font and falls back on some suitable alternatives if the defaults are not available. Implementation uses the Windows Unicode Runtime, and so requires OCaml 4.06.0 or later. Signed-off-by: David Allsopp <david.allsopp@metastack.com>
Ensure that type equations are also propagated.
The Windows Command prompt doesn't have an obvious equivalent to sh's eval, beyond the hideously ugly generation of a temporary .cmd script without setlocal at the start. Windows, however, has some moderately devious mechanisms to use an alternative. OpamStd.Win32.parent_putenv is the same as Unix.putenv, except that it manipulates the parent-process's environment (which, for opam.exe, will be the Command Prompt). The implementation relies on process injection which and takes into account Windows-on-Windows (i.e. 64/32-bit issues). In 64-bit Windows, a injecting 32-bit code into a 64-bit process will silently fail and injecting 64-bit code into a 32-bit process will kill the process. It is relatively easy for a 64-bit process to inject 32-bit code into its parent but it's incredibly hard to do the reverse and both mechanisms are fairly brittle (the curious reader is invited to search online for Heaven's Gate). In order to get around this problem, Windows builds of OPAM compile and install an additional small utility called opam-putenv. This utility is a 64-bit executable if OPAM is 32-bit and vice versa. An warning is displayed if the complementary compiler is not found - in this instance, OPAM must be run in a shell using the same architecture (meaning on 64-bit Windows, a 32-bit build of OPAM must be run from %windir%\SysWOW64\cmd.exe or it will display an error message). This implementation has the benefit, for Windows users, that unlike in other shells, it's not necessary to run opam config env at all after opam init, opam switch, etc. because OPAM can configure the environment instantly. This may prove to be the only place where Windows/CMD has the upper hand... This technique was inspired by Bill Stewart's editvar (see http://www.westmesatech.com/editv.html), a handy little utility primarily designed for prompting for passwords in Windows CMD scripts, which outputs the password entered by manipulating the parent's environment. Signed-off-by: David Allsopp <david.allsopp@metastack.com>
HOME is not commonly set in Windows installations. Wrap SHGetFolderPath to allow the user's Documents directory to be located and add a utility function for updating the HOME environment variable and persisting the change. Signed-off-by: David Allsopp <david.allsopp@metastack.com>
Clink (http://mridgers.github.io/clink) adds GNU readline support to the Windows Command Processor and exposes programmable completion via the Lua programming language. Refactor the support for OpamStd.Win32.parent_putenv to support invocation via Clink. The changes are: 1. OpamStd.Win32.set_parent_pid allows the pid written to by parent_putenv to be changed. This allows parent_putenv to behave us parent_parent_putenv when invoked by the Clink processor (the changes need to take place in the Windows Command Processor which is the parent our parent) 2. Generalising some of the C stubs so that parent_putenv becomes process_putenv and the machinery for detecting WoW64 and parent process is fully exposed as OpamStubs.getParentProcessID and OpamStubs.isWoW64Process (which replaces OpamStubs.get_mismatched_WoW64_ppid). 3. Adding a C stub for GetConsoleAlias (necessary for detecting where clink scripts are installed).
Great, thanks! |
This PR starts the process of merging some of the larger patches needed for proper native Windows support. My main focus here is to try to get things which will hugely affect both the API and build process in before the release of 2.0.0.
Windows support will necessarily require C stubs. The C code is provided in
src/stubs
but thejbuild
is only symlinked/copied on Windows, so this library is never built on Unix. opam-core is then altered toselect
between either a dummy implementation of the stubs for Unix or the stubs library for Windows. This may be replaced in the future as and when Dune gets better support for conditional compilation for os-specific things like this.Prior to Windows 10 1511, colour support is only available via specific Win32 API calls. Tempting as it may be to abandon pre-Windows 10 users, it's a bit mean as Windows 7 and 8 are still supported for quite a few more years. However, in order to translate VT100 sequences into the appropriate calls, it means that anything which may include an escape sequence must go via an
OpamConsole
function. This necessitates a few refactorings:OpamStd.Config.env
to accessOpamConsole.warning
which means that warning messages from that function display in colour everywhere.OpamStd.Format.print_table
toOpamConsole.print_table
. Morally, this function doesn't really have a home in either module - it could be implemented similarly to the trick inOpamStd.Config.env
with the circular dependency closed at runtime?A problem with re-routing all the functions is that
%a
has a different signature forprintf
vssprintf
which has affected the public API for some of these changes. It is possible to hook this while maintainingprintf
but it's a lot hairier.UTF-8 support on Windows is quietly improving - OCaml 4.06 added a lot and there's an open PR to push this support back to Windows 8 and earlier. The code here predates that work, but lots of it is still applicable, even with better support in OCaml. A key problem is that the default console fonts on Windows don't include the symbols required (I haven't yet found a good alternative font which actually includes them all - it's possible if we ask Microsoft nicely they might update the Consolas with a few of the missing ones in future...). The code is refactored to allow substitution with different symbols and there's quite a lot of C code for determining what the console font is and what glyphs it contains.
In fixing the compatibility layer so that
Uchar
andBuffer.add_utf_8_uchar
were available for 4.02.3, I noticed thatmodule type of
wasn't being "correctly", so that's fixed too.The following more minor things are also implemented in C:
Unix.getpid
does not return a unique value or the normal notion of "process ID" on Windows (this is known: see Mantis #4034). This affects opam's attempts to generate unique log and temporary filenames.Unix.wait
isn't available on Windows, but opam knows the list of child processes its launched, so it's possible to implementOpamProcess.wait_one
using the Win32WaitForMultipleObjects
call.opam init
has cause to write to the Windows Registry, so there's a stub for doing that.opam init
also has cause to offer to update theHOME
environment variable (Windows users won't have this set by default, but it's a good idea - other software packages pick up on it too). Changing this involves several API calls - one to retrieve the correct value to put inHOME
and another to broadcast to other Windows processes that the persisted environment has changed.Finally, there is a huge piece of machinery for the Windows equivalent of
eval $(opam env)
which is one area in which Windows is vastly superior to Unix, since it's never necessary for the user to run it. It is possible on Windows to runputenv
in the context of your parent (i.e. the shell) using process injection (which is an entirely standard thing to do on Windows - I got the idea from another tool I've used for years doing exactly the same thing). The only non-standard bit is that I've done the injection directly rather than the more conventional use of a DLL, but that's because callingSetEnvironmentVariable
is not a lot of code, so there's no need for the build system complexity of two DLLs. However, it's necessary to worry about whether you're a 32-bit opam running in a 64-bit shell and vice versa. On Windows, this means that an auxiliary C program is compiled calledopam-putenv.exe
which will always be for the opposite architecture fromopam.exe
. If there's a mismatch between opam and the shell, then it uses the auxiliary (which won't have a mismatch) to do the code injection and update the parent.It's really neat, and it's been happily working since 2015 when I wrote it!
There's then a refactoring commit for a lovely piece of software called Clink which adds GNU readline support to the Windows Command Processor and also allows prompt customisation and tab completion (both of which I've implemented, but which aren't in this PR). The refactoring is necessary because of how Clink works:
cmd.exe
starts which launches Clink and then clink has the equivalent of.bash_profile
scripts (the Windows Command Processor doesn't really have these itself) so this is the perfect place to ensure thatopam config env
is run every time you open a new command prompt (that's the only time it is necessary to runopam config env
, as at the moment I've chosen not to persist opam's environment changes... I may change my mind on that though!). At this point, when opam is setting the environment variables, it needs to runputenv
not in the parent process, but in the parent of its parent!This PR is probably best reviewed commit-by-commit rather than as one huge diff.