Build system

Managing big source files

We’ve compiled a small editor so far. The program is also small and not complicated yet. But if it grows bigger, it will be difficult to maintain. So, we should do the followings now.

Divide a C source file into two parts.

When you divide C source file into several parts, each file should contain one thing. For example, our source has two things, the definition of TfeTextView and functions related to GtkApplication and GtkApplicationWindow. It is a good idea to separate them into two files, tfetextview.c and tfe.c.

Now we have three source files, tfetextview.c, tfe.c and tfe3.ui. The 3 of tfe3.ui is like a version number. Managing version with filenames is one possible idea but it also has a problem. You need to rewrite filename in each version and it affects to contents of source files that refer to filenames. So, we should take 3 away from the filename.

In tfe.c the function tfe_text_view_new is invoked to create a TfeTextView instance. But it is defined in tfetextview.c, not tfe.c. The lack of the declaration (not definition) of tfe_text_view_new makes error when tfe.c is compiled. The declaration is necessary in tfe.c. Those public information is usually written in header files. It has .h suffix like tfetextview.h. And header files are included by C source files. For example, tfetextview.h is included by tfe.c.

The source files are shown below.

tfetextview.h

#include <gtk/gtk.h>

#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)

void
tfe_text_view_set_file (TfeTextView *tv, GFile *f);

GFile *
tfe_text_view_get_file (TfeTextView *tv);

GtkWidget *
tfe_text_view_new (void);

tfetextview.c

#include <gtk/gtk.h>
#include "tfetextview.h"

struct _TfeTextView
{
  GtkTextView parent;
  GFile *file;
};

G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);

static void
tfe_text_view_init (TfeTextView *tv) {
}

static void
tfe_text_view_class_init (TfeTextViewClass *class) {
}

void
tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
  tv->file = f;
}

GFile *
tfe_text_view_get_file (TfeTextView *tv) {
  return tv->file;
}

GtkWidget *
tfe_text_view_new (void) {
  return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
}

tfe.c

#include <gtk/gtk.h>
#include "tfetextview.h"

static void
app_activate (GApplication *app) {
  g_print ("You need a filename argument.\n");
}

static void
app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
  GtkWidget *win;
  GtkWidget *nb;
  GtkWidget *lab;
  GtkNotebookPage *nbp;
  GtkWidget *scr;
  GtkWidget *tv;
  GtkTextBuffer *tb;
  char *contents;
  gsize length;
  char *filename;
  int i;
  GError *err = NULL;
  GtkBuilder *build;

  build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/tfe.ui");
  win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
  gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
  nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
  g_object_unref (build);
  for (i = 0; i < n_files; i++) {
    if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, &err)) {
      scr = gtk_scrolled_window_new ();
      tv = tfe_text_view_new ();
      tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
      gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
      gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);

      tfe_text_view_set_file (TFE_TEXT_VIEW (tv),  g_file_dup (files[i]));
      gtk_text_buffer_set_text (tb, contents, length);
      g_free (contents);
      filename = g_file_get_basename (files[i]);
      lab = gtk_label_new (filename);
      gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
      nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
      g_object_set (nbp, "tab-expand", TRUE, NULL);
      g_free (filename);
    } else {
      g_printerr ("%s.\n", err->message);
      g_clear_error (&err);
    }
  }
  if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
    gtk_window_present (GTK_WINDOW (win));
  } else
    gtk_window_destroy (GTK_WINDOW (win));
}

int
main (int argc, char **argv) {
  GtkApplication *app;
  int stat;

  app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
  g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
  g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
  stat =g_application_run (G_APPLICATION (app), argc, argv);
  g_object_unref (app);
  return stat;
}

The ui file tfe.ui is the same as tfe3.ui in the previous section.

tfe.gresource.xml

<?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/com/github/ToshioCP/tfe">
    <file>tfe.ui</file>
  </gresource>
</gresources>

Dividing a file makes it easy to maintain. But now we face a new problem. The building step increases.

Build tools manage the steps.

Meson and Ninja

I’ll explain Meson and Ninja build tools.

Other possible tools are Make and Autotools. They are traditional tools but slower than Ninja. So, many developers use Meson and Ninja lately. For example, GTK 4 uses them.

You need to create meson.build file first.

project('tfe', 'c')

gtkdep = dependency('gtk4')

gnome=import('gnome')
resources = gnome.compile_resources('resources','tfe.gresource.xml')

sourcefiles=files('tfe.c', 'tfetextview.c')

executable('tfe', sourcefiles, resources, dependencies: gtkdep, install: false)

Now run meson and ninja.

$ meson setup _build
$ ninja -C _build

meson has two arguments.

Then, the executable file tfe is generated under the directory _build.

$ _build/tfe tfe.c tfetextview.c

A window appears. It includes a notebook with two pages. One is tfe.c and the other is tfetextview.c.

For further information, see The Meson Build system.