-
Notifications
You must be signed in to change notification settings - Fork 92
/
cmake-ide.el
1305 lines (1094 loc) · 52.2 KB
/
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; cmake-ide.el --- Calls CMake to find out include paths and other compiler flags -*- lexical-binding: t; -*-
;; Copyright (C) 2014 Atila Neves
;; Author: Atila Neves <atila.neves@gmail.com>
;; Version: 0.6
;; Package-Requires: ((emacs "24.4") (cl-lib "0.5") (seq "1.11") (levenshtein "0") (s "1.11.0"))
;; Keywords: languages
;; URL: http://github.com/atilaneves/cmake-ide
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This package runs CMake and sets variables for IDE-like functionality
;; provided by other packages such as:
;; On the fly syntax checks with flycheck
;; auto-completion using auto-complete-clang or company-clang
;; Jump to definition and refactoring with rtags
;; These other packages must be installed for the functionality to work
;;; Usage:
;; (cmake-ide-setup)
;;
;; If cmake-ide-flags-c or cmake-ide-flags-c++ are set, they will be added to ac-clang-flags.
;; These variables should be set. Particularly, they should contain the system include paths.
;;
;;; Code:
(require 'json)
(require 'find-file)
(require 'levenshtein)
(require 'cl-lib)
(require 'seq)
(require 's)
(require 'dash)
(defsubst cide--string-empty-p (string)
"Check whether STRING is empty."
(string= string ""))
(if (not (require 'subr-x nil t))
(progn
(message "`subr-x' not found, replacing string-empty-p with cide--string-empty-p")
(fset 'string-empty-p 'cide--string-empty-p))
(declare-function string-empty-p "subr-x"))
(declare-function rtags-call-rc "rtags")
(declare-function rtags-executable-find "rtags")
(declare-function irony-cdb-json-add-compile-commands-path "irony")
(declare-function flycheck-clear "flycheck")
(defcustom cmake-ide-flags-c
nil
"The C compiler flags to use. Should have -I flags for system includes."
:group 'cmake-ide
:type 'string
:safe #'stringp)
(defcustom cmake-ide-flags-c++
nil
"The C++ compiler flags to use. Should have -I flags for system includes."
:group 'cmake-ide
:type 'string
:safe #'stringp
)
(defcustom cmake-ide-dir
nil
"The build directory to run CMake in. If nil, runs in a temporary directory under `cmake-ide-build-pool-dir'. DEPRECATED, use `cmake-ide-build-dir' instead."
:group 'cmake-ide
:type 'directory
:safe #'stringp
)
(defcustom cmake-ide-build-dir
nil
"The build directory to run CMake in. If nil, runs in a temporary directory under `cmake-ide-build-pool-dir'."
:group 'cmake-ide
:type 'directory
:safe #'stringp
)
(defcustom cmake-ide-build-pool-dir
temporary-file-directory
"The parent directory for all automatically created build directories."
:group 'cmake-ide
:type 'directory
:safe #'stringp
)
(defcustom cmake-ide-build-pool-use-persistent-naming
nil
"Whether or not to use a persistent naming scheme for all automatically created build directories."
:group 'cmake-ide
:type 'boolean
:safe #'booleanp)
(defcustom cmake-ide-project-dir
nil
"The project directory."
:group 'cmake-ide
:type 'directory
:safe #'stringp)
(defcustom cmake-ide-compile-command
nil
"The command to use to compile the project. Can also include running tests."
:group 'cmake-ide
:safe #'stringp)
(defcustom cmake-ide-make-command
"make --no-print-directory"
"The command used to execute Makefile builds."
:group 'cmake-ide
:safe #'stringp)
(defcustom cmake-ide-ninja-command
"ninja"
"The command used to execute ninja type builds."
:group 'cmake-ide
:safe #'stringp)
(defcustom cmake-ide-cmake-command
"cmake"
"The command use to invoke cmake."
:group 'cmake-ide
:safe #'stringp)
(defcustom cmake-ide-cmake-opts
"-DCMAKE_BUILD_TYPE=Release"
"The options passed to cmake when calling it. DEPRECATED, use `cmake-ide-cmake-args' instead."
:group 'cmake-ide
:safe #'stringp)
(defcustom cmake-ide-cmake-args
nil
"List of options passed to cmake when calling it."
:group 'cmake-ide
:type '(repeat string)
:safe (lambda (val) (and (listp val) (-all-p 'stringp val))))
(defcustom cmake-ide-header-search-other-file
t
"Whether or not to search for a corresponding source file for headers when setting flags for them."
:group 'cmake-ide
:type 'boolean
:safe #'booleanp)
(defcustom cmake-ide-header-search-first-including
t
"Whether or not to search for the first source file to include a header when setting flags for them."
:group 'cmake-ide
:type 'boolean
:safe #'booleanp)
(defcustom cmake-ide-header-no-flags
nil
"Whether to apply compiler flags to header files. In some projects this takes too long."
:group 'cmake-ide
:type 'boolean
:safe #'booleanp)
(defcustom cmake-ide-flycheck-cppcheck-strict-standards
nil
"Whether or not to be strict when setting cppcheck standards for flycheck.
If 't' or otherwise non-nil, the `flycheck-cppcheck-standards'
variable will only be set to contain standards that exactly match
those from the compile database. (If there are none, it will not
be modified.) If 'nil', standards will be gracefully degraded to
the closest possible matches available in cppcheck."
:group 'cmake-ide
:type 'boolean
:safe #'booleanp)
;;; The buffers to set variables for
(defvar cide--src-buffers nil)
(defvar cide--hdr-buffers nil)
(defcustom cmake-ide-rdm-executable
"rdm"
"Location of rdm executable."
:group 'rtags
:type 'file)
(defcustom cmake-ide-rdm-rc-path
""
"Location of a custom rdm run control file."
:group 'cmake-ide
:type 'string
:safe #'stringp)
(defcustom cmake-ide-src-extensions
'(".c" ".cpp" ".C" ".cxx" ".cc")
"A list of file extensions that qualify as source files."
:group 'cmake-ide
:type '(repeat string))
(defcustom cmake-ide-ac-flags-to-filter
'("-m32" "-m64" "-Werror" "-c" "-fPIC" "-pipe" "-g" "-ggdb" "-march=native")
"Flags to remove from arguments passed to auto-completion."
:group 'cmake-ide
:type '(repeat string))
(defcustom cmake-ide-cmakelists-dir
nil
"The directory where the main CMakelists.txt is. DEPRECATED use `cmake-ide-projet-dir' instead."
:group 'cmake-ide
:type 'file)
(defvar cmake-ide-try-unique-compiler-flags-for-headers
nil
"Whether or not to try all unique compiler flags for header files."
)
(defvar cmake-sentinel-flag
nil
"One interactive execution is allowed at the same time."
)
(defvar cmake-temp-project-dir
nil
"The project dir is kept while the sentinel works."
)
(defun cide--make-hash-table ()
"Make a hash table with equal for the test function."
(make-hash-table :test #'equal))
(defvar cide--cache-dir-to-idb
(cide--make-hash-table)
"Key: build directory. Value: IDB for that build directory.")
(defvar cide--cache-dir-to-cdb-hash
(cide--make-hash-table)
"Key: build directory. Value: The hash of the JSON CDB.")
(defvar cide--cache-pkey-to-dir
(cide--make-hash-table)
"Key: project key. Value: build dir.")
;; Build dirs we've already told irony about
(defvar cide--cache-irony-dirs
(cide--make-hash-table)
"Used as a set. Key: build dir. Value: T or nil.")
(defvar cide--semantic-system-include)
(defconst cmake-ide-rdm-buffer-name "*rdm*" "The rdm buffer name.")
(defun cide--build-dir-var ()
"Return the value of `cmake-ide-build-dir' or `cmake-ide-dir'."
(let ((ret (or cmake-ide-build-dir cmake-ide-dir)))
(when ret (file-name-as-directory ret))))
(defun cide--project-dir-var ()
"Return the value of `cmake-ide-project-dir' or `cmake-ide-cmakelists-dir'."
(or cmake-ide-project-dir cmake-ide-cmakelists-dir))
(defun cide--mode-hook()
"Function to add to a major mode hook"
(add-hook 'find-file-hook #'cmake-ide-maybe-run-cmake nil 'local)
(cmake-ide-maybe-start-rdm))
;;;###autoload
(defun cmake-ide-setup ()
"Set up the Emacs hooks for working with CMake projects."
(add-hook 'c-mode-hook #'cide--mode-hook)
(add-hook 'c++-mode-hook #'cide--mode-hook)
;; When creating a file in Emacs, run CMake again to pick it up
(add-hook 'before-save-hook #'cide--before-save))
(defun cide--before-save ()
"When creating a file in Emacs, run CMake again to pick it up."
(when (and (cide--is-src-file (buffer-file-name))
(not (file-readable-p (buffer-file-name))))
(add-hook 'after-save-hook 'cide--new-file-saved nil 'local)))
(defun cide--new-file-saved ()
"Run CMake to pick up newly created files."
(cmake-ide-run-cmake)
(remove-hook 'after-save-hook 'cide--new-file-saved 'local))
;;;###autoload
(defun cmake-ide-maybe-run-cmake ()
"Run CMake if the compilation database JSON file is not found."
(interactive)
(when (cide--locate-project-dir)
(cmake-ide-maybe-start-rdm)
(if (cide--need-to-run-cmake)
(cmake-ide-run-cmake)
(progn
(cide--add-file-to-buffer-list)
(cide--on-cmake-finished)))))
(defun cide--add-file-to-buffer-list ()
"Add buffer to the appropriate list for when CMake finishes running."
(if (cide--is-src-file buffer-file-name)
(add-to-list 'cide--src-buffers (current-buffer))
(add-to-list 'cide--hdr-buffers (current-buffer))))
(defun cide--comp-db-file-name ()
"The name of the compilation database file."
(when (cide--build-dir)
(expand-file-name "compile_commands.json" (cide--build-dir))))
(defun cide--need-to-run-cmake ()
"If CMake needs to be run or not."
(and (not (get-process "cmake")) ; don't run if already running
(not (file-exists-p (cide--comp-db-file-name))))) ; no need if the file exists
;;;###autoload
(defun cmake-ide-run-cmake ()
"Run CMake and set compiler flags for auto-completion and flycheck.
This works by calling cmake in a temporary directory (or `cmake-ide-build-dir')
and parsing the JSON file deposited there with the compiler
flags."
(interactive)
(if (not cmake-sentinel-flag)
(when (buffer-file-name) ; if we call cmake-ide-run-cmake from a scatch buffer, do nothing
(when (file-readable-p (buffer-file-name)) ; new files need not apply
(save-some-buffers 1)
(let ((project-dir (cide--locate-project-dir)))
(if project-dir ; no point if it's not a CMake project
(if (not (file-exists-p (expand-file-name "CMakeCache.txt" project-dir)))
;; register this buffer to be either a header or source file
;; waiting for results
(progn
(cide--add-file-to-buffer-list)
;; run cmake only if project dir contains a CMakeLists.txt file.
(if (cide--locate-cmakelists)
(let ((cmake-dir (cide--build-dir)))
(let ((default-directory cmake-dir))
(cide--run-cmake-impl project-dir cmake-dir)
(cide--register-callback)
(setq cmake-temp-project-dir project-dir)
(setq cmake-sentinel-flag t)))
(cide--message "No CMakeLists.txt found in project dir, skip cmake run.")))
(cide--message "CMakeCache.txt found in project dir, skip cmake run."))
(cide--message "try to run cmake on a non cmake project [%s]" default-directory)))))
(cide--message "Another cmake is already running, skip cmake run.")))
(defun cide--message (str &rest vars)
"Output a message with STR and formatted by VARS."
(message (apply #'format (concat "cmake-ide [%s]: " str) (cons (current-time-string) vars))))
(defun cide--register-callback ()
"Register callback for when CMake finishes running."
(cide--register-a-callback
(lambda (process _event)
(cide--message "Finished running CMake")
(if (= 0 (process-exit-status process)) ; only perform post cmake operation on success.
(cide--on-cmake-finished)
(cide--message "CMake failed, see *cmake* for details."))
(setq cmake-sentinel-flag nil)
(setq cmake-temp-project-dir nil))))
(defun cide--register-a-callback (callback)
"Register CALLBACK to be called when CMake finishes running."
(set-process-sentinel (get-process "cmake") callback))
(defun cide--on-cmake-finished ()
"Set compiler flags for all buffers that requested it."
(let* ((idb (cide--cdb-json-file-to-idb))
(set-flags (lambda (x) (cide--set-flags-for-file idb x))))
(mapc set-flags cide--src-buffers)
(mapc set-flags cide--hdr-buffers)
(setq cide--src-buffers nil cide--hdr-buffers nil)
(cide--run-rc)))
;;;###autoload
(defun cmake-ide-load-db ()
"Load compilation DB and set flags for current buffer."
(interactive)
(if (not cmake-sentinel-flag)
(when (cide--locate-project-dir)
(cide--message "cmake-ide-load-db for file %s" (buffer-file-name))
(cmake-ide-maybe-start-rdm)
(let* ((file-name buffer-file-name)
(buffers (list (current-buffer)))
(cide--src-buffers (if (cide--is-src-file file-name) buffers nil))
(cide--hdr-buffers (if (cide--is-src-file file-name) nil buffers)))
(cide--on-cmake-finished)))
(cide--message "cmake is running, skip run.")))
(defvar cide--rdm-executable nil
"Rdm executable location path.")
(defun cmake-ide-rdm-executable ()
"Return rdm executable location path."
(cond (cide--rdm-executable cide--rdm-executable)
((file-exists-p cmake-ide-rdm-executable)
(setq cide--rdm-executable cmake-ide-rdm-executable)
cide--rdm-executable)
((featurep 'rtags)
(setq cide--rdm-executable (rtags-executable-find "rdm"))
cide--rdm-executable)
(t "rdm")))
(defun cide--run-rc ()
"Run rc to add definitions to the rtags daemon."
(when (featurep 'rtags)
(cmake-ide-maybe-start-rdm)
(cide--message "Running rc for rtags")
;; change buffer so as to not insert text into a working file buffer
(let ((cmake-ide-local-build-dir (cide--build-dir)))
(if (get-process "rdm")
(with-current-buffer (get-buffer cmake-ide-rdm-buffer-name)
(rtags-call-rc "-J" cmake-ide-local-build-dir))
(with-temp-buffer
(rtags-call-rc "-J" cmake-ide-local-build-dir))))))
(defun cide--set-flags-for-file (idb buffer)
"Set the compiler flags from IDB for BUFFER visiting file FILE-NAME."
(let* ((file-name (cide--get-buffer-file-name buffer))
(file-params (cide--idb-file-to-obj idb file-name))
(sys-includes (cide--params-to-sys-includes file-params))
(all-commands (cide--idb-all-commands idb))
(hdr-flags (cide--commands-to-hdr-flags all-commands)))
(cide--message "Setting flags for file %s" file-name)
;; set flags for all source files that registered
(if (cide--is-src-file file-name)
(cide--set-flags-for-src-file file-params buffer sys-includes)
(cide--set-flags-for-hdr-file idb buffer (cide--flags-to-sys-includes hdr-flags)))))
(defun cide--get-buffer-file-name (buffer)
"Get the name of a file for a given buffer."
(let ((file-name (buffer-file-name buffer)))
(cide--get-system-filename file-name)))
(defun cide--get-system-filename (file-name)
"Get the file name considering case sensitivity of the system."
(if (and file-name (eq system-type 'windows-nt))
(s-downcase file-name)
file-name))
(defun cide--set-flags-for-src-file (file-params buffer sys-includes)
"Set the compiler flags from FILE-PARAMS for source BUFFER with SYS-INCLUDES."
(let* ((src-flags (cide--params-to-src-flags file-params))
(src-includes (cide--params-to-src-includes file-params)))
(cmake-ide-set-compiler-flags buffer src-flags src-includes sys-includes)))
(defun cide--set-flags-for-hdr-file (idb buffer sys-includes)
"Set the compiler flags from IDB for header BUFFER with SYS-INCLUDES."
(when (and (not (string-empty-p (cide--buffer-string buffer))) (not cmake-ide-header-no-flags))
(cond
;; try all unique compiler flags until one successfully compiles the header
(cmake-ide-try-unique-compiler-flags-for-headers (cide--hdr-try-unique-compiler-flags idb buffer sys-includes))
;; ask ninja or make depending on what the user chose for the flags to use on the header
((cide--hdr-ask-ninja-and-make idb buffer sys-includes) t)
;; the default algorithm used so far
(t (cide--hdr-legacy idb buffer sys-includes)))))
(defun cide--buffer-string (buffer)
"Return the contents of BUFFER as a string."
(with-current-buffer buffer
(buffer-string)))
(defun cide--hdr-try-unique-compiler-flags (idb buffer sys-includes)
"Try all unique compiler flags in IDB in an attempt to find appropriate flags for header file in BUFFER using SYS-INCLUDES."
(let ((hdr-flags) (hdr-includes))
(setq hdr-flags (cide--idb-hdr-compiler-args idb (buffer-file-name buffer)))
(setq hdr-flags (cide--remove-compiler-from-args-string hdr-flags))
(setq hdr-includes (cide--flags-to-includes hdr-flags))
(cmake-ide-set-compiler-flags buffer hdr-flags hdr-includes sys-includes)))
(defun cide--hdr-ask-ninja-and-make (idb buffer sys-includes)
"Try to get compiler flags from IDB from a source file that depends on the header BUFFER using SYS-INCLUDES."
(let ((ninja-hdr-command (cide--ninja-header-command idb (buffer-file-name buffer))))
(if ninja-hdr-command
(progn
(cide--set-flags-for-hdr-exact buffer sys-includes ninja-hdr-command)
(cide--message "Setting flags for %s from ninja dependency information" (buffer-file-name buffer))
t) ;; has done something
nil)))
(defun cide--hdr-legacy (idb buffer sys-includes)
"Try to set compiler flags from IDB for header BUFFER using SYS-INCLUDES.
First, try to find a source file corresponding to the header.
Then, try to find a source file in IDB that directly includes the header.
If all else fails, use all compiler flags in the project."
(let* ((other (cide--src-file-for-hdr buffer))
(src-file-name (or other (cide--first-including-src-file idb buffer))))
(if src-file-name
;; if a source file is found, use its flags
(cide--set-flags-for-hdr-from-src idb buffer sys-includes src-file-name)
;; otherwise use flags from all source files
(cide--set-flags-for-hdr-from-all-flags idb buffer sys-includes))))
(defun cide--set-flags-for-hdr-exact (buffer sys-includes command)
"Set flags for BUFFER using SYS-INCLUDES and compiler COMMAND."
(let* ((hdr-flags (cide--remove-compiler-from-args-string command))
(hdr-includes (cide--flags-to-includes hdr-flags)))
(cmake-ide-set-compiler-flags buffer hdr-flags hdr-includes sys-includes)))
(defun cide--ninja-header-command (idb file-name)
"Return the command used by a file in IDB that depends on FILE-NAME.
Find an object file that lists FILE-NAME as a dependency, then return the first
compiler command in the project that has that object file in itself."
(let ((obj-file-name (cide--ninja-obj-file-depending-on-hdr file-name)))
(if (null obj-file-name) nil
(let ((all-commands (cide--idb-all-commands idb)))
(cide--filter-first (lambda (x) (string-match obj-file-name x))
all-commands)))))
(defun cide--ninja-obj-file-depending-on-hdr (file-name)
"Find the first object file that depends on the header FILE-NAME.
Ask ninja for all dependencies then find FILE-NAME in the output, returning
the object file's name just above."
(let ((default-directory (cide--build-dir))
(beg)
(end))
(if (not (file-exists-p (expand-file-name "build.ninja" default-directory)))
nil
(with-temp-buffer
(call-process cmake-ide-ninja-command nil t nil "-C" default-directory "-t" "deps")
(goto-char (point-min))
(setq beg (search-forward file-name nil t))
(if (null beg)
nil
(search-backward "#deps")
(setq beg (move-beginning-of-line nil))
(setq end (1- (search-forward ":")))
(copy-region-as-kill beg end)
(car kill-ring))))))
(defun cide--src-file-for-hdr (buffer)
"Try and find a source file for a header BUFFER (e.g. foo.cpp for foo.hpp)."
(if cmake-ide-header-search-other-file
(when (and buffer (buffer-live-p buffer))
(with-current-buffer buffer
(let ((other-file-name (ff-find-the-other-file)))
(if other-file-name (expand-file-name other-file-name) nil))))
nil))
(defun cide--set-flags-for-hdr-from-src (idb buffer sys-includes src-file-name)
"Use IDB to set flags for a header BUFFER with SYS-INCLUDES from its corresponding SRC-FILE-NAME."
(cide--message "Found src file %s for %s, using its flags" src-file-name (buffer-file-name buffer))
(cide--set-flags-for-src-file (cide--idb-file-to-obj idb src-file-name) buffer sys-includes))
(defun cide--first-including-src-file (idb buffer)
"Use IDB to find first source file that includes the header BUFFER."
(when (and (buffer-file-name buffer) cmake-ide-header-search-first-including)
(cide--message "Searching for source file including %s" (buffer-file-name buffer))
(let* ((file-name (buffer-file-name buffer))
ret-obj
ret-file-name)
(when (featurep 'rtags)
(setq ret-file-name
(with-temp-buffer
(rtags-call-rc "--dependencies" file-name "included-by" :noerror t)
(cide--filter-first
(lambda (a)
(gethash a idb))
(split-string (buffer-string) "\n" t split-string-default-separators)))))
(unless ret-file-name
(setq idb (cide--idb-sorted-by-file-distance idb file-name))
(setq ret-obj (cide--filter-first
(lambda (x) (cide--idb-obj-depends-on-file x file-name))
idb))
(when ret-obj (setq ret-file-name (cide--idb-obj-get ret-obj 'file))))
(when ret-file-name (cide--message "Found a source file including %s" file-name))
ret-file-name)))
(defun cide--read-file (path)
"Return PATH's file content as a string."
(if (stringp path)
(if (file-exists-p path)
(with-temp-buffer
(insert-file-contents path)
(buffer-string))
"")
(cide--message "cide--read-file ERROR: %s is not a string" path)))
(defun cide--set-flags-for-hdr-from-all-flags (idb buffer sys-includes)
"Use IDB to set flags from a header BUFFER with SYS-INCLUDES from all project source files."
(cide--message "Could not find suitable src file for %s, using all compiler flags" (buffer-file-name buffer))
(let* ((all-commands (cide--idb-all-commands idb))
(hdr-flags (cide--commands-to-hdr-flags all-commands))
(hdr-includes (cide--commands-to-hdr-includes all-commands)))
(cmake-ide-set-compiler-flags buffer hdr-flags hdr-includes sys-includes)))
(defun cmake-ide-set-compiler-flags (buffer flags includes sys-includes)
"Set ac-clang and flycheck variables for BUFFER from FLAGS, INCLUDES and SYS-INCLUDES."
;; FLAGS is a list of strings
(when (buffer-live-p buffer)
(with-current-buffer buffer
(when (featurep 'auto-complete-clang)
(make-local-variable 'ac-clang-flags)
(setq ac-clang-flags (cide--filter-ac-flags (cide--get-compiler-flags flags))))
(when (featurep 'company)
(make-local-variable 'company-clang-arguments)
(setq company-clang-arguments (cide--filter-ac-flags (cide--get-compiler-flags flags))))
(when (featurep 'company-c-headers)
(make-local-variable 'company-c-headers-path-user)
(setq company-c-headers-path-user (cide--flags-to-include-paths flags))
(make-local-variable 'company-c-headers-path-system)
(when sys-includes
(setq company-c-headers-path-system (append sys-includes company-c-headers-path-system))))
(when (and (featurep 'irony) (not (gethash (cide--build-dir) cide--cache-irony-dirs)))
(irony-cdb-json-add-compile-commands-path (cide--locate-project-dir) (cide--comp-db-file-name))
(puthash (cide--build-dir) t cide--cache-irony-dirs))
(when (featurep 'semantic)
(let ((dirs (cide--flags-to-include-paths flags)))
(when (boundp 'cide--semantic-system-include)
(mapc 'semantic-remove-system-include cide--semantic-system-include))
(mapc 'semantic-add-system-include dirs)
(setq-local cide--semantic-system-include dirs)))
(let ((macro-regex "\\(^-std=\\|\\.o$\\|^-o$\\)"))
(make-local-variable 'c-macro-cppflags)
(setq c-macro-cppflags
(mapconcat 'identity (cide--filter (lambda (x) (not (string-match macro-regex x)))
(cide--filter-ac-flags (cide--get-compiler-flags flags))) " ")))
(when (featurep 'flycheck)
(let* ((std-regex "^-std=")
(include-path (append sys-includes (cide--flags-to-include-paths flags)))
(definitions (append (cide--get-existing-definitions) (cide--flags-to-defines flags)))
(args (cide--filter (lambda (x) (not (string-match std-regex x))) (cide--flags-filtered (cide--get-compiler-flags flags)))))
(make-local-variable 'flycheck-clang-include-path)
(make-local-variable 'flycheck-gcc-include-path)
(setq flycheck-clang-include-path include-path)
(setq flycheck-gcc-include-path include-path)
(make-local-variable 'flycheck-clang-definitions)
(make-local-variable 'flycheck-gcc-definitions)
(setq flycheck-clang-definitions definitions)
(setq flycheck-gcc-definitions definitions)
(make-local-variable 'flycheck-clang-args)
(make-local-variable 'flycheck-gcc-args)
(setq flycheck-clang-args args)
(setq flycheck-gcc-args (cide--filter-output-arg args))
(setq flycheck-clang-tidy-build-path (cide--comp-db-file-name))
(make-local-variable 'flycheck-clang-language-standard)
(make-local-variable 'flycheck-gcc-language-standard)
(let* ((stds (cide--filter (lambda (x) (string-match std-regex x)) flags))
(repls (mapcar (lambda (x) (replace-regexp-in-string std-regex "" x)) stds)))
(when repls
(setq flycheck-clang-language-standard (car repls))
(setq flycheck-gcc-language-standard (car repls))
(unless cmake-ide-flycheck-cppcheck-strict-standards
(setq repls (mapcar 'cide--cmake-standard-to-cppcheck-standard repls)))
(setq repls (cide--filter 'cide--valid-cppcheck-standard-p repls))
(when repls
(make-local-variable 'flycheck-cppcheck-standards)
(setq flycheck-cppcheck-standards repls))))
(make-local-variable 'flycheck-cppcheck-include-path)
(setq flycheck-cppcheck-include-path (append sys-includes (cide--flags-to-include-paths flags))))
(setq flycheck-clang-includes includes)
(setq flycheck-gcc-includes includes)
(flycheck-clear)
(when (bound-and-true-p flycheck-mode)
(run-at-time "0.5 sec" nil 'flycheck-buffer))))))
(defun cmake-ide-delete-file ()
"Remove file connected to current buffer and kill buffer, then run CMake."
(interactive)
(if (not cmake-sentinel-flag)
(when (cide--locate-project-dir)
(if (cide--build-dir)
(let ((filename (buffer-file-name))
(buffer (current-buffer))
(name (buffer-name)))
(if (not (and filename (file-exists-p filename)))
(error "Buffer '%s' is not visiting a file!" name)
(when (yes-or-no-p "Are you sure you want to remove this file? ")
(delete-file filename)
(kill-buffer buffer)
(let ((project-dir (cide--locate-project-dir)))
(when (and project-dir (file-exists-p (expand-file-name "CMakeLists.txt" project-dir)))
(cide--run-cmake-impl project-dir (cide--build-dir)))
(cide--message "File '%s' successfully removed" filename)))))
(error "Not possible to delete a file without setting cmake-ide-build-dir")))
(cide--message "cmake is running, skip run.")))
(defun cide--run-cmake-impl (project-dir cmake-dir)
"Run the CMake process for PROJECT-DIR in CMAKE-DIR."
(when project-dir
(let ((default-directory cmake-dir))
(cide--message "Running cmake for src path %s in build path %s" project-dir cmake-dir)
(apply 'start-process (append (list "cmake" "*cmake*" cmake-ide-cmake-command)
(cide--cmake-args)
(list "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
"-B" "." "-S" project-dir))))))
(defun cide--project-key ()
"Return a unique key for a project based on the project dir and cmake options."
(let ((project-dir (cide--locate-project-dir)))
(when project-dir
;; if no project-dir, then get-project-key is called from a non cmake project dir, simply ignore
(replace-regexp-in-string "[-/= ]" "_" (concat (expand-file-name project-dir)
(string-join (cide--cmake-args) " "))))))
(defun cide--cmake-args ()
"Return a list of arguments to pass to CMake when calling it."
(or cmake-ide-cmake-args (split-string cmake-ide-cmake-opts)))
(defun cide--build-dir ()
"Return the directory name to run CMake in."
(let ((build-dir-base
(or (cide--build-dir-var) ; if use set, use this value (may be relative)
(cide--build-dir-from-cache)))) ; else get from project-key (return an absolute path)
(when build-dir-base
(let ((build-dir (expand-file-name build-dir-base
(cide--locate-project-dir)))) ; if relative, use project-dir as base directory
(when (not (file-accessible-directory-p build-dir))
(cide--message "Making directory %s" build-dir)
(make-directory build-dir 't))
(file-name-as-directory build-dir)))))
(defun cide--build-dir-from-cache ()
"Get the build dir from the cache if there or compute if not.
Return nil for non-CMake project."
(let ((project-key (cide--project-key)))
(when project-key
(let ((build-dir (gethash project-key cide--cache-pkey-to-dir nil)))
(or build-dir
(let ((build-parent-directory (or cmake-ide-build-pool-dir temporary-file-directory))
(build-directory-name (if cmake-ide-build-pool-use-persistent-naming
project-key
(make-temp-name "cmake"))))
(setq build-dir (expand-file-name build-directory-name build-parent-directory))
(progn
(puthash project-key build-dir cide--cache-pkey-to-dir))
build-dir))))))
(defun cide--is-src-file (name)
"Test if NAME is a source file or not."
(cl-some (lambda (x) (string-suffix-p x name)) cmake-ide-src-extensions))
(defun cide--filter (pred seq)
"Apply PRED to filter SEQ."
(delq nil
(mapcar (lambda (x) (and (funcall pred x) x)) seq)))
(defun cide--filter-first (pred seq)
"Return first element to satisfy PRED in SEQ."
(let ((index 0) (ret))
(while (and (null ret) (< index (length seq)))
(when (funcall pred (elt seq index))
(setq ret (elt seq index)))
(cl-incf index))
ret))
(defun cide--filter-params (file-params filter-func)
"Filter FILE-PARAMS with FILTER-FUNC."
;; The compilation database is a JSON array of JSON objects
;; Each object is a file with directory, file and command fields
;; Depending on FILTER-FUNC, it maps file names to desired compiler flags
;; An example would be -I include flags
(let* ((args (cide--file-params-to-args file-params))
(flags (funcall filter-func args)))
(mapconcat 'identity flags " ")))
(defun cide--file-params-to-args (file-params)
"Get the compiler command arguments from FILE-PARAMS."
;; file-params is also known as an object in the codebase
(let ((command (cide--idb-obj-get file-params 'command))
(arguments (cide--idb-obj-get file-params 'arguments)))
(cide--resolve-response-file
(if command
(mapcar #'cide--quote-if-spaces (cide--split-command command))
(cide--vector-to-list arguments)))))
(defun cide--resolve-response-file (argument-list)
"Matches response file string and adds its content to the object parameters."
(-flatten (mapcar #'cide--replace-response-file argument-list)))
(defun cide--replace-response-file (argument)
"Matches a response file in string ARGUMENT returning a list of arguments"
(if (not (stringp argument))
argument
(if (string-match "@[^[:space:]]+" argument)
(let* ((response-file (substring argument 1))
(file-params (cide--get-file-params response-file)))
(mapcar #'cide--quote-if-spaces (cide--split-command file-params)))
argument)))
(defun cide--get-file-params (response-file)
"Get file parameters from a response file given as compilation argument."
(replace-regexp-in-string "\\\n" " " (cide--read-file (expand-file-name response-file (cide--build-dir)))))
(defun cide--quote-if-spaces (str)
"Add quotes to STR if it has spaces."
(if (string-match-p " " str)
(concat "\"" str "\"")
str))
(defun cide--vector-to-list (vector)
"Convert VECTOR to a list."
(append vector nil))
(defun cide--args-to-only-flags (args)
"Get compiler flags from ARGS."
(cide--filter (lambda (x) (not (cide--is-src-file (cide--unquote x)))) args))
(defun cide--unquote (x)
"Possibly unquote a string X."
(if (and (stringp x) (> (length x) 2))
(if (and (equal (elt x 0) ?\") (equal (elt x (1- (length x))) ?\"))
(cl-subseq x 1 (1- (length x)))
x)
x))
(defun cide--json-unescape (str)
"Remove JSON-escaped backslashes in STR."
(let* ((no-double-backslashes (replace-regexp-in-string "\\\\\\\\" "\\\\" str))
(no-backslash-quote (replace-regexp-in-string "\\\\\"" "\"" no-double-backslashes)))
no-backslash-quote))
(defun cide--params-to-src-flags (file-params &optional filter-func)
"Source compiler flags for FILE-PARAMS using FILTER-FUNC."
(if (not file-params) nil
(let* ((filter-func (or filter-func #'cide--args-to-only-flags))
(value (cide--filter-params file-params filter-func))
(flags-string (if value value nil)))
(if flags-string (cide--cleanup-flags-str flags-string) nil))))
(defun cide--cleanup-flags-str (str)
"Clean up and filter STR to yield a list of compiler flags."
(let ((unescaped-flags-string (cide--json-unescape str)))
(cide--remove-compiler-from-args-string unescaped-flags-string)))
(defun cide--remove-compiler-from-args-string (str)
"Remove the compiler command from STR, leaving only arguments. Return a list of strings."
(let ((args (cide--split-command str)))
(cide--remove-compiler-from-args args)))
(defun cide--remove-compiler-from-args (args)
"Remove the compiler command from ARGS, leaving only arguments."
(if (string-suffix-p "ccache" (car args))
(cddr args)
(cdr args)))
(defun cide--filter-ac-flags (flags)
"Filter unwanted compiler arguments out from FLAGS."
(cide--filter
(lambda (x)
(cl-loop for flag in cmake-ide-ac-flags-to-filter
never (string-match (format "^%s$" flag) x)))
flags))
(defun cide--delete-dup-hdr-flags (flags)
"Delete duplicates in FLAGS for header files."
(let* ((rest (cide--flags-filtered flags))
(dashes (cide--filter #'cide--dash-i-or-dash-d-p flags)))
(append (delete-dups dashes) rest)))
(defun cide--commands-to-hdr-flags (commands)
"Header compiler flags from COMMANDS."
(let* ((args (cide--flatten (mapcar #'cide--remove-compiler-from-args-string commands)))
(flags (cide--args-to-only-flags args)))
(setq flags (cide--filter (lambda (x) (not (equal x "-o"))) flags))
(setq flags (cide--filter (lambda (x) (not (string-suffix-p ".o" x))) flags))
(setq flags (cide--filter (lambda (x) (not (string-suffix-p ".obj" x))) flags))
(cide--delete-dup-hdr-flags flags)))
(defun cide--params-to-src-includes (file-params)
"-include compiler flags for from FILE-PARAMS."
(cide--flags-to-includes (cide--params-to-src-flags file-params 'identity)))
(defun cide--params-to-sys-includes (file-params)
"-include compiler flags for from FILE-PARAMS."
(cide--flags-to-sys-includes (cide--params-to-src-flags file-params 'identity)))
(defun cide--commands-to-hdr-includes (commands)
"Header `-include` flags from COMMANDS."
(let ((args (cide--flatten (mapcar #'cide--remove-compiler-from-args-string commands))))
(delete-dups (cide--flags-to-includes args))))
(defun cide--flatten (lst)
"Flatten LST."
(if (equal (length lst) 1)
(elt lst 0)
(apply #'append lst)))
(defun cide--flags-to-include-paths (flags)
"From FLAGS (a list of flags) to a list of include paths."
(let ((raw-paths (cide--to-simple-flags flags "^-I")))
(mapcar (lambda (x) (expand-file-name x (cide--build-dir))) raw-paths)))
(defun cide--flags-to-defines (flags)
"From FLAGS (a list of flags) to a list of defines."
(cide--to-simple-flags flags "^-D"))
(defun cide--to-simple-flags (flags flag)
"A list of either directories or defines from FLAGS depending on FLAG."
(let* ((case-fold-search nil)
(res-flags (cide--filter
(lambda (x)
(let ((match (string-match flag x)))
(and match (zerop match))))
flags)))
(mapcar (lambda (x) (replace-regexp-in-string flag "" x)) res-flags)))
(defun cide--flags-to-includes (flags)
"From FLAGS (a list of flags) to a list of includes."
(let ((includes nil))
(while (member "-include" flags)
(setq flags (cdr (member "-include" flags)))
(when flags (setq includes (cons (car flags) includes))))
includes))
(defun cide--flags-to-sys-includes (flags)
"From FLAGS (a list of flags) to a list of isystem includes."
(let ((sysincludes nil))
(while (member "-isystem" flags)
(setq flags (cdr (member "-isystem" flags)))
(when flags
(if (member (car flags) sysincludes)
nil
(setq sysincludes (cons (car flags) sysincludes)))))
sysincludes))
(defun cide--dash-i-or-dash-d-p (flag)
"If FLAG is -I or -D."
(let* ((case-fold-search nil)
(imatch (string-match "^-I" flag))
(dmatch (string-match "^-D" flag)))
(or imatch dmatch)))
(defun cide--flags-filtered (flags)
"Filter out defines and includes from FLAGS."
(cide--filter (lambda (x) (not (cide--dash-i-or-dash-d-p x))) flags))
(defun cide--get-compiler-flags (flags)
"Use FLAGS to return all compiler flags including existing ones.
Returns a list of strings."
(condition-case nil
;; Assume flags is a string
(append (split-string (cide--get-existing-compiler-flags)) flags)
;; On exception
(wrong-type-argument
;; Treat as list of strings.
(append (cide--get-existing-compiler-flags) flags))))
(defun cide--get-existing-compiler-flags ()
"Return existing ac-clang flags for this mode, if set.
Returns a string.
"
(if (eq major-mode 'c++-mode)
(cide--symbol-value 'cmake-ide-flags-c++)
(cide--symbol-value 'cmake-ide-flags-c)))
(defun cide--get-existing-definitions ()
"Return existing compiler defines, if set."
(cide--symbol-value 'cmake-ide-definitions))