So what if I wanted to write some C with NO Swift?

It’s been awhile since I wrote much C, and I certainly don’t have a solid VScode workflow for it.

I made a sample multi-file C project and I love that it’s 61.4% makefile and 38.6% C

https://github.com/carlynorama/Hello_C_using_VScode

References

Steps taken

  1. (VSCode, XCode command line tools already installed.)
  2. Installed Microsoft C/C++ extension
  3. made a folder, opened it
  4. added a .gitignore (see below)
  5. made hello_world.c (see below)
  6. hit the debug button and selected C tool (clang, e.g.)
    • look at tasks.json file created
  7. Saw output in debug window! That was easy! Nice!
  8. try adding second_file.c and second_file.h …. CLONK…
Undefined symbols for architecture arm64:
  "_and_so_we_meet_again", referenced from:
      _main in hello_world-5211a2.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
  1. manual compile check
clang hello_world.c second_file.c
./a.out

… worked fine. Do I need to add a make file?

  1. Yes. I need to do one of two things

    • A) in tasks.json change "{$file}", to "hello_world.c", "second_file.c", which I did and it works just fine.
    • B) OR add a make file. (later)
  2. F5 to start the process of making a launch.json? Sure. See video.

  3. make some small edits to launch.json, especially "preLaunchTask": "C/C++: clang build active file"

— (Later)

  1. create a test makefile (see makefiles_for_testing/test), run make and make clean to confirm make is installed and working.

  2. Get a real Makefile working

  3. Add a task to tasks.json, add it launch.json

TODO: figure out adding CMake. https://code.visualstudio.com/docs/cpp/cmake-linux

Files

hello_world.c

#include <stdio.h>
#include "second_file.h"

int main(int argc, char *argv[]) {
    printf("Hello World.");
    and_so_we_meet_again();
}

second_file.c

#include <stdio.h>

#include "second_file.h"

void and_so_we_meet_again() {
    printf("... and so we meet ...again?");
}

second_file.h

#pragma once

void and_so_we_meet_again();

final Makefile

CC=clang
CFLAGS=-c -Wall
LDFLAGS=
SOURCES=hello_world.c second_file.c
OBJDIR=obj
_OBJECTS=$(SOURCES:.c=.o)
OBJECTS = $(patsubst %,$(OBJDIR)/%,$(_OBJECTS))
EXECUTABLE=hello_world
DEST_DIR=bin


all: $(SOURCES) $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
	$(CC) $(LDFLAGS) $(OBJECTS) -o $(DEST_DIR)/$@

$(OBJDIR)/%.o: %.c
	$(CC) $(CFLAGS) $< -o $@

# `make clean` removes contents of bin folder and any random .o files
clean:
	rm -rf $(OBJDIR)/*.o
	rm -rf $(DEST_DIR)/$(EXECUTABLE)

.gitignore

.DS_Store

#name of compiled program
hello_world

/*.dSYM
*.out
/bin
/obj

# in this example, go ahead and show the .vscode folder
#/.vscode


# C/C++ Files object files 
*.o 
*.a 
*.so

VSCode Helper files

tasks.json

Created when I hit the debug button for the first time.

The first task, cppbuild, is no longer being used by me, but I’ve left it in for the record.

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: clang build active file",
            "command": "/usr/bin/clang",
            "args": [
                "-fcolor-diagnostics",
                "-fansi-escape-codes",
                "-g",
                "hello_world.c",
                "second_file.c",
                "-o",
                "${fileDirname}/bin/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": false
            },
            "detail": "Task generated by Debugger."
        },
        {
            "type": "process", //should this be done as shell or process? right now no args
            "label": "Build using make",
            "command": "make",
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": [
                "$gcc"
            ],
            "detail": "What does adding a detail do?"
        },
    ],
    "version": "2.0.0"
}

launch.json

This is the file that triggers building the makefile task via "preLaunchTask": "Build using make"

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(lldb) Launch",
            "type": "cppdbg",
            "request": "launch",
            //change this line to match tasks.json
            "program": "${workspaceFolder}/bin/hello_world",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            //TBD on which I prefer.
            //"externalConsole": true,
            "externalConsole": false,
            "MIMode": "lldb",
            //added to compile before launch
            //also needs to match tasks.json
            "preLaunchTask": "Build using make",    

        }

    ]
}

Make Files for testing

hello make

all: one two three
one:
	echo “create file one”
	touch one
two:
	echo “create file two”
	touch two
three:
	echo “create file three”
	touch three
clean:
	echo “clean up files”
	rm -f one two three

super basic

# Always compiles everything.

#none of the calls are file names, so lets keep make from getting confused.
.PHONY: clean simple verbose

# `make` or `make simple` will compile the files into output file after -o
simple: hello_world.c second_file.c
	clang hello_world.c second_file.c -o bin/hello_world

# `make verbose` will compile the files with -g (debugging information) and -Wall (compiler warnings)	
verbose: hello_world.c second_file.c
	clang -g -Wall hello_world.c second_file.c  -o bin/hello_world

# `make clean` removes contents of bin folder, there isn't a "clean"
clean:
	rm -rf bin/*

explicit two file set up

all: hello_world

# compiles main program
hello_world: hello_world.c second_file.o
	clang -g -Wall hello_world.c bin/second_file.o -o bin/hello_world

# will compile second file if needed
second_file.o: second_file.c second_file.h
	clang -g -Wall -c second_file.c -o bin/second_file.o

# `make clean` removes contents of bin folder
# run even if there is a file called clean
.PHONY: clean
clean:
	rm -rf bin/*

Start adding variables

CC=clang
CFLAGS=-c -Wall
LDFLAGS=
SOURCES=hello_world.c second_file.c
OBJECTS=$(SOURCES:.c=.o)
EXECUTABLE=hello

all: $(SOURCES) $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
	$(CC) $(LDFLAGS) $(OBJECTS) -o $@

.c.o:
	$(CC) $(CFLAGS) $< -o $@

# `make clean` removes .o files and compiled program
# run even if there is a file called clean
.PHONY: clean
clean:
	rm -f *.o $(EXECUTABLE)

Put built files in sub folders

CC=clang
CFLAGS=-c -Wall
LDFLAGS=
SOURCES=hello_world.c second_file.c
OBJDIR=obj
_OBJECTS=$(SOURCES:.c=.o)
OBJECTS = $(patsubst %,$(OBJDIR)/%,$(_OBJECTS))
EXECUTABLE=hello
DEST_DIR=bin


all: $(SOURCES) $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
	$(CC) $(LDFLAGS) $(OBJECTS) -o $(DEST_DIR)/$@

$(OBJDIR)/%.o: %.c
	$(CC) $(CFLAGS) $< -o $@

# `make clean` removes contents of bin folder and any random .o files
# run even if there is a file called clean
.PHONY: clean
clean:
	rm -rf *.o
	rm -rf $(DEST_DIR)/*