merlin icon indicating copy to clipboard operation
merlin copied to clipboard

dot-merlin not found: cygwin + emacs-w32 + fdopen's opam

Open ttamttam opened this issue 9 years ago • 11 comments

When opening a .ml file in emacs, tuareg and merlin modes are loaded as expected. But merlin seems not to find my .merlin file:

  • it complains about unbound module until I "use a package" from the menu
  • when I "dot-merlin check" from the menu, the answer is "No .merlin loaded"

I'm not sure how to solve this problem? I do nothing about the protocol between emacs and ocamlmerlin, but I suspect that somehow, the path of the project or file is sent to ocamlmerlin? Would it be possible that the problem would be a mistmatch between windows and cygwin path format?

Thank you for any help.

I'm describing the installation steps below:

I installed cygwin and opam from https://fdopen.github.io/opam-repository-mingw/. I choosed 32 bits flavour.

I installed tuareg, utop and merlin with opam:

opam install tuareg utop merlin

Since I'm using emacs-w32 package from cygwin, I downloaded https://www.emacswiki.org/emacs/download/cygwin-mount.el and https://www.emacswiki.org/emacs/download/windows-path.el. I put them in ~/.emacs.d/ and added in ~/.emacs:

(require 'cygwin-mount "~/.emacs.d/cygwin-mount.el")
(require 'windows-path "~/.emacs.d/windows-path.el")
(windows-path-activate)

Here is the last part of my emacs init file:

(
 let ((opam-share
       (ignore-errors (car (process-lines "opam" "config" "var" "share")))))
  (when (and opam-share (file-directory-p opam-share))

    (add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share))

    (load "tuareg-site-file")

    (autoload 'utop "utop" "Toplevel for OCaml" t)
    (setq utop-command "utop -emacs")
    (autoload 'utop-minor-mode "utop" "Minor mode for utop" t)
    (add-hook 'tuareg-mode-hook 'utop-minor-mode)

    (load "merlin")
    (autoload 'merlin-mode "merlin" nil t nil)
    (add-hook 'tuareg-mode-hook 'merlin-mode t)
    (setq merlin-command  'opam)

    )
  )

ttamttam avatar Nov 08 '16 13:11 ttamttam

Could you share the directory setup? Is the .merlin is the same direcotry as ML files or a parent directory?

let-def avatar Nov 08 '16 14:11 let-def

It is in the same directory as the ML files. Here is an example:

$ ls -al
total 6
drwxr-xr-x+ 1 vagrant        None  0 Nov  8 16:04 .
drwxrwxr-x+ 1 Administrators None  0 Nov  8 15:59 ..
-rw-r--r--+ 1 vagrant        None 25 Nov  8 15:59 .merlin
-rw-r--r--+ 1 vagrant        None 33 Nov  8 16:04 test.ml

vagrant@ocamlcompilation /cygdrive/c/Users/vagrant/Desktop/test
$ cat .merlin
S .
B _build
PKG rresult

vagrant@ocamlcompilation /cygdrive/c/Users/vagrant/Desktop/test
$ cat test.ml
open Rresult

let ok = R.ok true

ttamttam avatar Nov 08 '16 15:11 ttamttam

Ok. Nothing seems wrong at first sight, it seems I will need access to a windows machine to reproduce. Can you describe the cygwin setup? Do you have a VM that could be easy to test (I say that because of the vagrant user :)).

let-def avatar Nov 08 '16 15:11 let-def

Well. My VM disk is 8 GiB, and my internet connection is slow…

But here are the recipes to reproduce, as one powershell script to install cygwin, and some bash scripts. Beware that the last two scripts are untested.

cygwin installation

Powershell script:

function Download-File { param ([string]$url, [string]$file)
  Write-Output "Downloading `'$url`' to `'$file`'"
  $downloader = new-object System.Net.WebClient
  $downloader.Proxy.Credentials=[System.Net.CredentialCache]::DefaultNetworkCredentials;
  $downloader.DownloadFile($url, $file)
}

function Ensure-Tempdir {
  if ($env:TEMP -eq $null) {
    $env:TEMP = Join-Path $env:SystemDrive 'temp'
  }
  if (![System.IO.Directory]::Exists($env:TEMP)) {
    [System.IO.Directory]::CreateDirectory($env:TEMP)
  }
}

Ensure-Tempdir

$cygwinSetupFile = Join-Path $env:TEMP "setup-x86.exe"

Download-File "http://cygwin.com/setup-x86.exe" $cygwinSetupFile

$PACKAGES="-P mingw64-i686-binutils,mingw64-i686-gcc-core,mingw64-i686-gcc-g++,mingw64-i686-runtime -P autoconf,make,diffutils,patch,dos2unix,m4 -P p7zip,unzip,zip -P git,emacs-w32,wget,cpio -P libncurses-devel,ncurses -P mingw64-i686-pkg-config,mingw64-i686-libffi -P curl"

Start-Process -Wait $cygwinSetupFile -ArgumentList "-qnNdO -R `"c:/cygwin`" -s `"http://cygwin.uib.no`" $PACKAGES"

opam

Bash script:

OPAM32="opam32.tar.xz"
[ -e ${OPAM32} ] || curl -Lo ${OPAM32} https://dl.dropboxusercontent.com/s/eo4igttab8ipyle/${OPAM32}
tar xaf ${OPAM32}
bash opam32/install.sh
opam init  --comp 4.03.0+mingw32c --switch 4.03.0+mingw32c -a mingw "https://github.com/fdopen/opam-repository-mingw.git"
opam install tuareg utop merlin

emacs conf

This part is not tested. I should have tested… Problem: the two curl calls are not working, because the addresses are pointing to web pages instead of elisp files. To be downloaded by hand.

Bash script:

mkdir .emacs.d
curl -Lo .emacs.d/cygwin-mount.el https://www.emacswiki.org/emacs/download/cygwin-mount.el
curl -Lo .emacs.d/windows-path.el https://www.emacswiki.org/emacs/download/windows-path.el
cat >> .emacs <<EOF
;; Basic .emacs with a good set of defaults, to be used as template for usage
;; with OCaml and OPAM
;;
;; Author: Louis Gesbert <[email protected]>
;; Released under CC0

;; Generic, recommended configuration options


;; Added by Package.el.  This must come before configurations of
;; installed packages.  Don't delete this line.  If you don't want it,
;; just comment it out by adding a semicolon to the start of the line.
;; You may delete these explanatory comments.
(package-initialize)

(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(ac-use-fuzzy nil)
 '(backup-directory-alist (quote (("." . "~/.local/share/emacs/backups"))))
 '(compilation-context-lines 2)
 '(compilation-error-screen-columns nil)
 '(compilation-scroll-output t)
 '(compilation-search-path (quote (nil "src")))
 '(electric-indent-mode nil)
 '(indent-tabs-mode nil)
 '(line-move-visual t)
 '(next-error-highlight t)
 '(next-error-highlight-no-select t)
 '(next-line-add-newlines nil)
 '(require-final-newline t)
 '(sentence-end-double-space nil)
 '(show-paren-mode t)
 '(show-trailing-whitespace t)
 '(visible-bell t))

(desktop-save-mode 1)

;; ANSI color in compilation buffer
(require 'ansi-color)
(defun colorize-compilation-buffer ()
  (toggle-read-only)
  (ansi-color-apply-on-region (point-min) (point-max))
  (toggle-read-only))
(add-hook 'compilation-filter-hook 'colorize-compilation-buffer)

;; Some key bindings

;; (global-set-key [f3] 'next-match)
;; (defun prev-match () (interactive nil) (next-match -1))
;; (global-set-key [(shift f3)] 'prev-match)
;; (global-set-key [backtab] 'auto-complete)
;; ;; OCaml configuration
;; ;;  - better error and backtrace matching

;; (defun set-ocaml-error-regexp ()
;;   (set
;;    'compilation-error-regexp-alist
;;    (list '("[Ff]ile \\(\"\\(.*?\\)\", line \\(-?[0-9]+\\)\\(, characters \\(-?[0-9]+\\)-\\([0-9]+\\)\\)?\\)\\(:\n\\(\\(Warning .*?\\)\\|\\(Error\\)\\):\\)?"
;;     2 3 (5 . 6) (9 . 11) 1 (8 compilation-message-face)))))

;; (add-hook 'tuareg-mode-hook 'set-ocaml-error-regexp)
;; (add-hook 'caml-mode-hook 'set-ocaml-error-regexp)

(require 'cygwin-mount "~/.emacs.d/cygwin-mount.el")
(require 'windows-path "~/.emacs.d/windows-path.el")
(windows-path-activate)

(
 let ((opam-share
       (ignore-errors (car (process-lines "opam" "config" "var" "share")))))
  (when (and opam-share (file-directory-p opam-share))

    (add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share))

    (load "tuareg-site-file")

    (autoload 'utop "utop" "Toplevel for OCaml" t)
    (setq utop-command "utop -emacs")
    (autoload 'utop-minor-mode "utop" "Minor mode for utop" t)
    (add-hook 'tuareg-mode-hook 'utop-minor-mode)

    (load "merlin")
    (autoload 'merlin-mode "merlin" nil t nil)
    (add-hook 'tuareg-mode-hook 'merlin-mode t)
    (setq merlin-command  'opam)

    )
  )


(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 )
EOF

small test project

This script is not tested

Bash script:

mkdir test
cat >> test/.merlin <<EOF
S .
B _build
PKG rresult
EOF
cat >> test/test.ml <<EOF2
open Rresult

let ok = R.ok true
EOF2

ttamttam avatar Nov 08 '16 15:11 ttamttam

I did some experiments after I defined a log file. Here is a fragment of the log file, which seems to confirm my hypothesis:

# 0.00 frontend - input
{
  "document": [ "auto", "/home/MDT/test/test.ml" ],
  "query": [ "protocol", "version", 3 ]
}
# 0.00 dot_merlin - load filenames
[ "/home/MDT/test" ]
# 0.00 frontend - output
{
  "class": "return",
  "value": {
    "selected": 3,
    "latest": 3,
    "merlin": "The Merlin toolkit version 2.5.1, for Ocaml 4.03.0"
  },
  "notifications": []
}
# 0.00 frontend - input
{
  "document": [ "auto", "/home/MDT/test/test.ml" ],
  "query": [ "tell", "start", "end", "open Rresult\n\nlet ok = R.ok true\n" ]
}
# 0.00 dot_merlin - update filenames
[ "/home\\MDT\\test" ]
# 0.00 dot_merlin - update files
[]
# 0.00 frontend - output
{ "class": "return", "value": true, "notifications": [] }
# 0.00 dot_merlin - load filenames
[]

I have the feeling that this could be solved from emacs, by adapting the filename transmitted to ocamlmerlin somehow?

ttamttam avatar Nov 17 '16 13:11 ttamttam

As a workaround, is there any way to manually specify a project file?

leviroth avatar Jun 13 '17 14:06 leviroth

There as been a lot of windows specific changes, see http://www.dra27.uk/blog/platform/2017/08/24/merlin-3-on-windows.html . Can you try with latest version?

let-def avatar Aug 28 '17 09:08 let-def

I just upgraded to Merlin 3, and it's been working when I use fdopen's ocaml-env-win to run non-Cygwin Emacs (spacemacs). However, Cygwin's own Vim installation doesn't find the .merlin file, as before.

Edit: I have the same problem when using Cygwin's version of Emacs.

leviroth avatar Aug 29 '17 18:08 leviroth

I just had the same problem. I edited merlin.el and changed the following line:

                   (cons "-filename" filename))

to:

                   (cons "-filename" (cygwin-convert-file-name-to-windows filename)))

and it fixed the issue.

ghost avatar Sep 22 '17 10:09 ghost

I had this problem again… and understood why. A idea for a simple workaround is proposed at the end. If ok, I could do it.

Here is my new configuration:

  • OCaml 4.12.0+mingw64c (with @fdopen opam’s repository)
  • Native Emacs, started from cygwin with ocaml-env-win
  • With the latest spacemacs (develop branch) with the following addition at the beginning of init.el:
    (setq shell-file-name "bash")
    (setq explicit-shell-file-name shell-file-name)
    

When I execute merlin-type-enclosing, after a while, I obtain merlin: error (end-of-file) trying to parse answer: in the minibuffer.

If I merlin-enable-debug, I obtain this:

# calling binary: "C:/OCaml64/home/MatthieuDubuget/.opam/4120/bin/ocamlmerlin" with arguments: ("server" "type-enclosing" "-protocol" "sexp" "-log-file" "-" "-verbosity" "1" "-filename" "c:/OCaml64/home/MatthieuDubuget/presence_mts/lib/badges.ml" "-position" "162:9" "-index" "0").
# stdout
# stderr
execlp: No error
merlin path: c:\OCaml64\home\MatthieuDubuget\.opam\4120\bin\ocamlmerlin-server.exe
socket path: /tmp/\\.\pipe\ocamlmerlin_Matthieu Dubuget_56115b02_290000000389c5

Oh! The socket path seems strange to me: because of the ’ ’ in the middle!

I opam source merlin, adapt src/frontend/ocamlmerlin/ocamlmerlin.c to use a fix user (around line 538).

And bingo! It works!

Workaround proposition: replace spaces chars from user with ’_’ before building the pipe name? (pull request: https://github.com/ocaml/merlin/pull/1345)

Best regards

ttamttam avatar May 22 '21 15:05 ttamttam

I was apparently a bit lazy when I wrote the original function (I guess I was writing a lot of C to get the port working!). It would be better to be using the current user's SID (strings like S-1-5-21-992878714-4041223874-2616370337-1001 - the actual Windows equivalent of a uid), but it takes a few more API calls to do that. The benefit is that SIDs are actually unique (most of my computers, for example, have machine\DRA and domain\DRA simultaneously) and definitely don't contain spaces.

It feels like if Emacs can't be fixed to handle a space in the socket name (it really can't?!) then it would be better to switch to SIDs rather than mangling the name to create something potentially even less unique?

It doesn't create an excuse on the OCaml side not to improve things, but OCaml on Windows is already difficult to use if you have a username with a space in it because Cygwin struggles with that too.

dra27 avatar Jun 07 '21 18:06 dra27