-
Notifications
You must be signed in to change notification settings - Fork 1
/
ox-cmake-ide.el
272 lines (211 loc) · 8.32 KB
/
ox-cmake-ide.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
;; -*- lexical-binding: t -*-
(require 'json)
(require 'cl-lib)
(require 'seq)
(defun oxci--make-hash-table()
(make-hash-table :test #'equal))
(cl-defstruct oxci--project
initial
projectroot
commandfile
buildroot
flagmap
buffers)
(defcustom oxci--build-subdir
"build"
"Default build directory name")
(defvar oxci--project-map
(oxci--make-hash-table)
"Keeps track of cmake projects")
(clrhash oxci--project-map)
;;;###autoload
(defun ox-cmake-ide()
(add-hook 'c-mode-hook #'oxci--setup-buffer t)
(add-hook 'c++-mode-hook #'oxci--setup-buffer t))
(defun oxci--project--setup-irony(project)
(let ((projectroot (oxci--project-projectroot project)))
; remove previous entries about this projectroot
(setq irony-cdb-json--project-alist
(seq-filter (lambda (item)
(not (string= (car item) projectroot)))
irony-cdb-json--project-alist))
; add new mapping for project root to compile_commands.json
(irony-cdb-json-add-compile-commands-path
(oxci--project-projectroot project)
(oxci--commandfile-from-project project))))
(defun oxci--create-project(root)
(let (project)
(setq project (make-oxci--project
:initial t
:projectroot root
:flagmap (oxci--make-hash-table)
:buffers '()))
(puthash root project oxci--project-map)
(oxci--project--setup-irony project)
project))
(defun oxci--project-from-buffer(&optional buffer)
(unless buffer
(setq buffer (current-buffer)))
(let (filename
filedir
projectroot
project)
(setq filename (buffer-file-name buffer))
(setq filedir (expand-file-name ".." filename))
; try find root
(setq projectroot (expand-file-name (locate-dominating-file filedir ".git")))
(when (string-empty-p projectroot)
(setq projectroot (expand-file-name (locate-dominating-file filedir "build"))))
(when (string-empty-p projectroot)
(setq projectroot (expand-file-name (locate-dominating-file filedir "CMakeLists.txt"))))
(message (format "Project root %s" projectroot))
(when projectroot
(setq project (gethash projectroot oxci--project-map))
(unless project
(setq project (oxci--create-project projectroot)))
project)))
(defun oxci--buildroot-from-project(project)
(let ((buildroot (oxci--project-buildroot project)))
(unless buildroot
(setq buildroot (expand-file-name oxci--build-subdir (oxci--project-projectroot project)))
(setf (oxci--project-buildroot project) buildroot))
buildroot))
(defun oxci--commandfile-from-project(project)
(let ((commandfile (oxci--project-commandfile project)))
(unless commandfile
(setq commandfile (expand-file-name "compile_commands.json" (oxci--buildroot-from-project project))))
commandfile))
(defun oxci--run-cmake--finished(project process event)
(message "oxci: finished cmake process with exit-code %d" (process-exit-status process))
(let ((flagmap (oxci--project-flagmap project)))
(clrhash flagmap))
(oxci--parse-compile-flags project)
(oxci--apply-flags project))
(defun oxci--get-param-from-flag(param flags)
(let (result
keepnext)
(seq-do
(lambda (flag)
(if keepnext
(progn
(setq keepnext nil)
(push flag result))
(cond ((string-match-p (concat "^" param ".") flag)
(push (replace-regexp-in-string (concat "^" param) "" flag) result))
((string-match-p (concat "^" param) flag)
(setq keepnext t)))))
flags)
result))
(defun oxci--filter-param-from-flag(param flags)
(let (removenext)
(seq-filter
(lambda (flag)
(if removenext
(progn
(setq removenext nil)
nil)
(cond ((string-match-p (concat "^" param ".") flag)
nil)
((string-match-p (concat "^" param) flag)
(setq removenext t)
nil)
(t t))))
flags)))
(defun oxci--get-flags-without-output(flags)
(oxci--filter-param-from-flag "-o" flags))
(defun oxci--get-defines(flags)
(oxci--get-param-from-flag "-D" flags))
(defun oxci--get-includes(buildroot flags)
(seq-map (lambda (path)
(expand-file-name path buildroot))
(oxci--get-param-from-flag "-\\(I\\|include\\)" flags)))
(defun oxci--parse-compile-flags(project)
(let ((commandfile (oxci--commandfile-from-project project))
(flagmap (oxci--project-flagmap project)))
(when (file-exists-p commandfile)
(message (format "oxci: parsing commandfile %s" commandfile))
(setq json (json-read-file commandfile))
(seq-doseq (entry json)
(let ((filename (cdr (assoc 'file entry)))
(directory (cdr (assoc 'directory entry)))
(command (cdr (assoc 'command entry)))
(flags nil))
(setq flags (split-string-and-unquote (replace-regexp-in-string "\\\\\"" "\"" command)))
(puthash filename flags flagmap))))))
(defun oxci--apply-flags(&optional project buffer)
"(Re-)apply compile flags to a buffer"
(interactive)
(unless project
(setq project (oxci--project-from-buffer buffer))
(setq buffer (current-buffer)))
(if (not project)
(oxci--setup-buffer buffer)
; parse flags once
(let ((flagmap (oxci--project-flagmap project)))
(unless (and flagmap
(> (hash-table-count flagmap) 0))
(setq flagmap (oxci--parse-compile-flags project))
(setf (oxci--project-flagmap project) flagmap))
(if (not buffer)
; apply flags for every buffer in project
(seq-map (lambda(buffer) (oxci--apply-flags project buffer)) (oxci--project-buffers project))
; apply flags to single buffer
(let ((filename (buffer-file-name buffer))
(buildroot (oxci--buildroot-from-project project))
flags
compiler
includes
defines
gcc-flags)
(setq flags (gethash filename flagmap))
(setq compiler (car flags))
(setq flags (cdr flags))
(when (featurep 'company)
(make-local-variable 'company-clang-arguments)
(setq company-clang-arguments flags))
(when (featurep 'flycheck)
(flycheck-irony-setup)))))))
(defun oxci--run-cmake(&optional project)
"Run cmake for a project or the current buffer's project"
(interactive)
(unless project
(setq project (oxci--project-from-buffer (current-buffer))))
(let ((projectroot (oxci--project-projectroot project))
(buildroot (oxci--buildroot-from-project project))
cmake-process)
(let ((default-directory buildroot))
(message (format "oxci: running cmake inside %s" buildroot))
(setq cmake-process (start-process "oxci-cmake" "*cmake*" "cmake" projectroot "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"))
(set-process-sentinel
cmake-process
(lambda (process event)
(oxci--run-cmake--finished project process event))))))
(defun oxci--forget-buffer(buffer)
(let ((project (oxci--project-from-buffer buffer))
fileslist)
(setq fileslist (oxci--project-buffers project))
(delete buffer fileslist)))
(defun oxci--setup-buffer(&optional buffer)
"Add a file to a managed project"
(interactive)
(unless buffer
(setq buffer (current-buffer)))
(let ((project (oxci--project-from-buffer))
buildroot
commandfile
fileslist)
(when project
(setq buildroot (oxci--buildroot-from-project project))
(setq commandfile (oxci--commandfile-from-project project))
; add local hook for removing buffer from the list on kill
(add-hook 'kill-buffer-hook (lambda() (oxci--forget-buffer buffer)) nil t)
; add file to project's buffer list
(setq fileslist (oxci--project-buffers project))
(add-to-list 'fileslist buffer)
(setf (oxci--project-buffers project) fileslist)
(message (format "oxci: added file %s to project at %s" (buffer-file-name buffer) (oxci--project-projectroot project)))
(if (or (oxci--project-initial project)
(not (file-exists-p commandfile)))
(progn (setf (oxci--project-initial project) nil)
(oxci--run-cmake project))
(oxci--apply-flags project buffer)))))