bluTaz
bluTaz

Reputation: 323

What is the quickest way to compile and run any single C file

I'd like to compile and run a single c source file on a keypress such as <F5>, now I've looked at several ways of accomplishing this and most involve using a Makefile. However, every example I've found on a generic Makefile require editing the TARGET or compiling all source files in the working directory. This led me to the question if I could run a regexp and replace the first line of the Makefile from any source file I'm working on.

  1. Press <F5>
    1. Update Makefile in root projectdir with current <file>.c working on.
    2. Run make ONLY on <file>

There may be a simpler way of accomplishing this without editing the Makefile, but I've exhausted myself with trying to make a generic enough makefile to do it.

The regexp I have that would select the correct line:

^TARGET\s[=]\s.*

Upvotes: 1

Views: 2990

Answers (4)

Rorschach
Rorschach

Reputation: 32446

Here is a simplified version of a function I bind to C-c C-C in my c-mode-map. It just compiles the file, runs it with output to the compilation buffer, and then removes the compiled program. Note that you can string together multiple shell commands in your compile-command to run/cleanup etc. With a prefix it will prompt for compilation command as normal.

(defun my-compile-and-run (arg)
  "Compile, run (output to compilation buffer), remove program."
  (interactive "P")
  (let* ((out (concat (file-name-sans-extension
                       (file-name-nondirectory buffer-file-name))))
         (command
          (concat "gcc -std=c11 "       ;other flags, etc.
                  buffer-file-name " -o " out
                  "; ./" out            ;run executable
                  "; rm " out)))        ;clean up
    (unless arg
      (setq-local compilation-read-command nil))
    (setq-local compile-command command)
    (call-interactively 'compile)))

You can get more creative with your compile-command as well. If you actually wanted to run it through a makefile, for example,

CURRENT ?=
EXE     = $(CURRENT:.c=)

target: ${CURRENT}
    gcc -ansi -Wall -pedantic -O3 ${CURRENT} -o ${EXE}

run-target: target
    ${EXE}

clean-target:
    $(RM) ${EXE}

You could then compile from emacs by just setting the compile-command to set the CURRENT variable to the buffers name and call whichever make targets you want, eg.

(defun my-compile-and-run (arg)
  "Compile, run (output to compilation buffer), remove program."
  (interactive "P")
  (when (file-exists-p "Makefile")
    (let ((command (concat "make CURRENT=\"" ;set current target
                           (shell-quote-argument buffer-file-name)
                           ;run and clean program
                           "\" run-target clean-target"))) 
      (unless arg
        (setq-local compilation-read-command nil))
      (setq-local compile-command command)
      (call-interactively 'compile))))

Typically you want to set the compile-command in you mode-hooks or as a local variable like @phils shows in the .dirs-locals.el or the other alternative ways to define emacs local variables.

Upvotes: 0

jpkotta
jpkotta

Reputation: 9437

I use multi-compile. Here's my config:

(use-package multi-compile
  :config
  (defun locate-repo-dir (&optional file-or-dir)
    "Find the root of the version control repository."
    (let* ((file-or-dir (or file-or-dir (buffer-file-name) default-directory))
           (file-dir (if (file-directory-p file-or-dir)
                         file-or-dir
                       (file-name-directory file-or-dir)))
           (root-dir (vc-call-backend (vc-deduce-backend) 'root file-dir)))
      root-dir))

  (dolist (e '(("%cflags" . (or (getenv "CFLAGS") "-Wall -g3 -std=c11"))
               ("%cxxflags" . (or (getenv "CXXFLAGS") "-Wall -g3"))
               ("%repo-dir" . (locate-repo-dir))))
    (add-to-list 'multi-compile-template e))

  (setq multi-compile-alist
        '((".*" . (("make-simple" .
                    "make -k")
                   ("make-repo" .
                    "make -k --no-print-directory -C '%repo-dir'")
                   ("make-top" .
                    "make -k --no-print-directory -C '%make-dir'")))
          (c-mode . (("c-simple" .
                      "gcc -o '%file-sans' %cflags '%file-name'")
                     ("c-simple32" .
                      "gcc -o '%file-sans' %cflags -m32 '%file-name'")))
          (c++-mode . (("c++-simple" .
                        "g++ -o '%file-sans' %cxxflags '%file-name'")))))

  :bind (("C-c b" . multi-compile-run))
  )

The c-simple option is exactly what I use for single-file C programs.

Upvotes: 1

phils
phils

Reputation: 73344

You could create a .dir-locals.el file in your project directory with something like:

((c-mode . ((eval . (setq-local compile-command
                                (format "gcc -std=c99 -Wall -Wextra -Wpedantic -Werror %s"
                                        (shell-quote-argument (file-name-nondirectory
                                                               buffer-file-name))))))))

Then you can just bind <f5> to compile and it will do the appropriate thing.

Making compile do "the right thing" means that you can consistently use the same command in all projects whenever you need to compile (or perform any vaguely analogous activity, depending on what the current project happens to be).

Upvotes: 2

ad absurdum
ad absurdum

Reputation: 21364

For testing small programs, I often use this bash script that cleans up after itself:

#!/bin/bash
gcc -std=c99 -Wall -Wextra -Wpedantic -Werror $1 -o tempout &&\
    ./tempout && rm tempout

I have this saved in an executable file called crepl, and I can use it by entering at the command line:

> crepl my_program.c

I use the -Werror flag so that code with warnings will not execute. This is pretty primitive, and won't work when libraries must be linked, but it is useful for simple small programs.

Upvotes: 1

Related Questions