十、Gtk4-Build system

本文介绍了如何管理和组织C语言项目的源文件,强调了将大文件拆分为多个部分的重要性,并讨论了使用Meson和Ninja、Make以及Rake等构建工具进行编译和构建流程的管理。通过示例展示了如何创建头文件、分割源代码以及使用不同构建系统自动化编译过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 管理多个源文件

到目前为止,我们已经编译了一个小型编辑器。但一些不好的迹象已经开始出现。

  • 我们只有一个C源文件,并将所有内容都放在其中。我们需要解决这个问题。
  • 有两个编译器,gcc和glib-compile-resources。我们应该通过一种构建工具来控制它们。

这些思想对于管理大型源文件很有用。

2 将C源文件分成两部分

当你把C源文件分成几个部分时,每个文件应该包含一个东西。例如,我们的源代码有两件事,TfeTextView的定义和与GtkApplication和GtkApplicationWindow相关的函数。将它们分成两个文件,tfetextview.c和tfe.c是一个好主意。

  • tfetextview.c包含了TfeTextView的定义和函数。
  • tfe.c包含main、app_activate、app_open等函数,它们与GtkApplication和GtkApplicationWindow相关

现在我们有三个源文件:tfetextview.c、tfe.c和tfe3.ui。tfe3的3就像一个版本号。通过文件名管理版本是一种可能的想法,但它可能会带来麻烦。您需要在每个版本中重写文件名,它会影响引用文件名的源文件的内容。因此,我们应该从文件名中去掉3。

在tfe.c中调用函数tfe_text_view_new来创建一个TfeTextView实例。但是它是在tfetextview.c中定义的,而不是tfe.c。没有tfe_text_view_new的声明(而不是定义)会在编译tfe.c时出错。该声明在tfe.c中是必要的。这些公开信息通常写在头文件中。它有.h后缀,像tfetextview.h,头文件包含在C源文件中。例如,tfetextview.h包含在tfe.c中。

下面列出了所有的源文件。

/* filename: tfetextview.h */
 1 #include <gtk/gtk.h>
 2 
 3 #define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
 4 G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)
 5 
 6 void
 7 tfe_text_view_set_file (TfeTextView *tv, GFile *f);
 8 
 9 GFile *
10 tfe_text_view_get_file (TfeTextView *tv);
11 
12 GtkWidget *
13 tfe_text_view_new (void);
14 
/* filename: tfetextview.c */
 1 #include <gtk/gtk.h>
 2 #include "tfetextview.h"
 3 
 4 struct _TfeTextView
 5 {
 6   GtkTextView parent;
 7   GFile *file;
 8 };
 9 
10 G_DEFINE_TYPE (TfeTextView, tfe_text_view, GTK_TYPE_TEXT_VIEW);
11 
12 static void
13 tfe_text_view_init (TfeTextView *tv) {
14 }
15 
16 static void
17 tfe_text_view_class_init (TfeTextViewClass *class) {
18 }
19 
20 void
21 tfe_text_view_set_file (TfeTextView *tv, GFile *f) {
22   tv -> file = f;
23 }
24 
25 GFile *
26 tfe_text_view_get_file (TfeTextView *tv) {
27   return tv -> file;
28 }
29 
30 GtkWidget *
31 tfe_text_view_new (void) {
32   return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, NULL));
33 }
34 
/* filename: tfe.c */
 1 #include <gtk/gtk.h>
 2 #include "tfetextview.h"
 3 
 4 static void
 5 app_activate (GApplication *app) {
 6   g_print ("You need a filename argument.\n");
 7 }
 8 
 9 static void
10 app_open (GApplication *app, GFile ** files, gint n_files, gchar *hint) {
11   GtkWidget *win;
12   GtkWidget *nb;
13   GtkWidget *lab;
14   GtkNotebookPage *nbp;
15   GtkWidget *scr;
16   GtkWidget *tv;
17   GtkTextBuffer *tb;
18   char *contents;
19   gsize length;
20   char *filename;
21   int i;
22   GtkBuilder *build;
23 
24   build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe3/tfe.ui");
25   win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
26   gtk_window_set_application (GTK_WINDOW (win), GTK_APPLICATION (app));
27   nb = GTK_WIDGET (gtk_builder_get_object (build, "nb"));
28   g_object_unref (build);
29   for (i = 0; i < n_files; i++) {
30     if (g_file_load_contents (files[i], NULL, &contents, &length, NULL, NULL)) {
31       scr = gtk_scrolled_window_new ();
32       tv = tfe_text_view_new ();
33       tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
34       gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (tv), GTK_WRAP_WORD_CHAR);
35       gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scr), tv);
36 
37       tfe_text_view_set_file (TFE_TEXT_VIEW (tv),  g_file_dup (files[i]));
38       gtk_text_buffer_set_text (tb, contents, length);
39       g_free (contents);
40       filename = g_file_get_basename (files[i]);
41       lab = gtk_label_new (filename);
42       gtk_notebook_append_page (GTK_NOTEBOOK (nb), scr, lab);
43       nbp = gtk_notebook_get_page (GTK_NOTEBOOK (nb), scr);
44       g_object_set (nbp, "tab-expand", TRUE, NULL);
45       g_free (filename);
46     } else if ((filename = g_file_get_path (files[i])) != NULL) {
47         g_print ("No such file: %s.\n", filename);
48         g_free (filename);
49     } else
50         g_print ("No valid file is given\n");
51   }
52   if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (nb)) > 0) {
53     gtk_window_present (GTK_WINDOW (win));
54   } else
55     gtk_window_destroy (GTK_WINDOW (win));
56 }
57 
58 int
59 main (int argc, char **argv) {
60   GtkApplication *app;
61   int stat;
62 
63   app = gtk_application_new ("com.github.ToshioCP.tfe", G_APPLICATION_HANDLES_OPEN);
64   g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
65   g_signal_connect (app, "open", G_CALLBACK (app_open), NULL);
66   stat =g_application_run (G_APPLICATION (app), argc, argv);
67   g_object_unref (app);
68   return stat;
69 }
70 

ui文件tfe.ui与tfe3文件夹内相同。在上一节中介绍。

# filename: tfe.gresource.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <gresources>
3   <gresource prefix="/com/github/ToshioCP/tfe3">
4     <file>tfe.ui</file>
5   </gresource>
6 </gresources>

分文件使维护源文件变得容易。但是现在我们面临着一个新的问题。building步骤增加。

  • 编译ui文件tfe.ui。将其转换为resources.c。
  • 将tfe.c编译为tfe。O(目标文件)。
  • 编译tfetextview.c为tfetextview.o。
  • 将resources.c编译为resources.o。
  • 将所有目标文件链接到应用程序tfe。

构建工具管理这些步骤。我将向你展示三种构建工具,Meson和Ninja,Make和Rake。推荐将Meson和Ninja作为C编译工具,其他的也可以。这是你的选择。

3 Meson and Ninja

Meson和Ninja是构建C语言程序最流行的构建工具之一。最近许多开发人员使用Meson和Ninja。例如,GTK 4使用它们。

首先,你需要去编写 meson.build文件。

 1 project('tfe', 'c')
 2 # dependency = pkg-config
 3 gtkdep = dependency('gtk4')
 4 
 5 gnome=import('gnome')
 6 resources = gnome.compile_resources('resources','tfe.gresource.xml')
 7 
 8 sourcefiles=files('tfe.c', 'tfetextview.c')
 9 
10 executable('tfe', sourcefiles, resources, dependencies: gtkdep)
  • 1:函数project定义关于项目的东西。第一个参数是项目名称,第二个参数是编程语言。
  • 2:依赖函数定义了pkg-config使用的依赖。我们将gtk4作为参数。
    5: import功能导入模块。在第5行中,导入了gnome模块,并将其赋值给变量gnome。gnome模块提供了构建GTK程序的辅助工具。
    6: .compile_resources是gnome模块的一个方法,在XML文件的指令下将文件编译为资源。在第6行中,资源文件名是resources,即resources.c和resources.h,而xml文件是tfe.gresource.xml。这个方法默认生成C源文件。
  • 8:定义源文件。
  • 10:表示可执行函数,通过编译源文件生成目标文件。第一个参数是目标的文件名。以下参数均为源文件。最后一个参数是一个选项依赖项。gtkdep用于编译。

现在运行meson和ninja命令:

$ meson _build
$ ninja -C _build

这时,可执行文件tfe已经在_build目录中生成。

$ _build/tfe tfe.c tfetextview.c

出现一个窗口。它包括一个两页的笔记本。一个是tfe.c,另一个是tfetextview.c。

有关更多信息,请参阅The Meson Build system。

4 Make

Make是创建于1976年的构建工具。它是C编译的标准构建工具,但最近被Meson和Ninja取代。

Make分析Makefile文件,然后执行编译器。所有的指令都写在Makefile中。

例如,

sample.o: sample.c
    gcc -o sample.o sample.c

上面的Malefile由三个元素组成:sample.o, sample.c和gcc -o sample.o sample.c。

  • sample.o是一个目标。
  • sample.c是先决条件。
  • gcc -o sample.o sample.c是一个recipe。recipes遵循制表符,而不是空格。这很重要。使用tab,否则make不会像你预期的那样工作)。

规则是:

如果先决条件在目标之后修改,make就会执行recipe。

在上面的例子中,如果sample.c在sample.o生成后被修改。然后make执行gcc并将sample.c编译为sample.o。如果sample.c的修改时间较早于sample.o。那么不需要编译,所以make什么都不做。

tfe的Makefile如下所示:

 1 all: tfe
 2 
 3 tfe: tfe.o tfetextview.o resources.o
 4 	gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`
 5 
 6 tfe.o: tfe.c tfetextview.h
 7 	gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
 8 tfetextview.o: tfetextview.c tfetextview.h
 9 	gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
10 resources.o: resources.c
11 	gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
12 
13 resources.c: tfe.gresource.xml tfe.ui
14 	glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
15 
16 .Phony: clean
17 
18 clean:
19 	rm -f tfe tfe.o tfetextview.o resources.o resources.c

你输入make,然后所有事情都会被执行:

$ make
gcc -c -o tfe.o `pkg-config --cflags gtk4` tfe.c
gcc -c -o tfetextview.o `pkg-config --cflags gtk4` tfetextview.c
glib-compile-resources tfe.gresource.xml --target=resources.c --generate-source
gcc -c -o resources.o `pkg-config --cflags gtk4` resources.c
gcc -o tfe tfe.o tfetextview.o resources.o `pkg-config --libs gtk4`

我只使用了非常基本的规则来编写这个Makefile。还有许多更方便的方法可以使它更紧凑。但要解释清楚需要很长时间。我想以make结束今天的内容,然后进入下一个话题。

你可以从GNU网站下载“Gnu Make Manual”。

5 Rake

Rake也是一个类似make的程序。它是用Ruby语言编写的。如果你不使用Ruby,则不需要阅读这一小节。然而,Ruby确实是非常复杂和值得推荐的脚本语言。

  • Rakefile控制rake的行为。
  • 你可以在Rakefile中编写任何Ruby代码。

Rake有任务和文件任务,与make中的目标、前提和recipe类似。

 1 require 'rake/clean'
 2 
 3 targetfile = "tfe"
 4 srcfiles = FileList["tfe.c", "tfetextview.c", "resources.c"]
 5 uifile = "tfe.ui"
 6 rscfile = srcfiles[2]
 7 objfiles = srcfiles.ext(".o")
 8 gresource_xml = "tfe.gresource.xml"
 9 
10 CLEAN.include(targetfile, objfiles, rscfile)
11 
12 task default: targetfile
13 
14 file targetfile => objfiles do |t|
15   sh "gcc -o #{t.name} #{t.prerequisites.join(' ')} `pkg-config --libs gtk4`"
16 end
17 
18 objfiles.each do |obj|
19   src = obj.ext(".c")
20   file obj => src do |t|
21     sh "gcc -c -o #{t.name} `pkg-config --cflags gtk4` #{t.source}"
22   end
23 end
24 
25 file rscfile => uifile do |t|
26   sh "glib-compile-resources #{gresource_xml} --target=#{t.name} --generate-source"
27 end

Rakefile的内容几乎与前一小节中的Makefile相同。

  • 3-8:定义目标文件、源文件等。
  • 1、10需要rake/clean库。clean文件被添加到CLEAN。当在命令行中输入rake CLEAN时,CLEAN包含的文件将被删除。
  • 12:默认目标依赖于targetfile。任务默认值是任务的最终目标。
  • 14-16: targetfile依赖于objfiles。变量t是一个任务对象。
  • t.name是目标名称
  • t.prerequisites是先决条件的数组。
  • t.source是先决条件的第一个元素。
  • sh是一个方法,它将下面的字符串作为参数传递给shell并执行shell。
  • 18-23:数组objfiles的each迭代器。每个对象都依赖于相应的源文件。
  • 25-27:资源文件依赖于ui文件。

Rakefile对于初学者来说似乎很难。但是,您可以在Rakefile中使用任何Ruby语法,因此它非常灵活。如果你使用Ruby和Rakefile,它将是一个高效的工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值