This page is a guide for the rapidly growing interests from developers in China. They have Linux background and want to develop for MeeGo. This guide is written in Chinese and serves as a start point for them.
Contents |
MeeGo 官方的虚拟键盘和输入法开发框架是 Maliit。它的架构如下图所示,蓝色的部分表示 Mallit 的组件。
第三方开发者可以选择开发完全独立的 keyboard 部分 (包括 engine),也可以只开发 engine 给默认的 keyboard 使用。
关于如何开发第三方的虚拟键盘等,请参考 Maliit/Documentation。
理想情况下,当虚拟键盘弹出时,为了避免虚拟键盘遮住输入区域,应用程序应当自动改变输入控件的位置或者改变窗口的大小。这部分处理应当在应用程序所使用的 toolkit (即 meego-ux-components) 内自动完成,不需要应用程序干预。所以,GUI 应用程序应当使用 meego-ux-components 进行开发。详细信息请参考下一节。
Maliit 0.20.0 版本于三月初发布,从这个版本开始,它的 API 中去除了 MTF 依赖。如果你的虚拟键盘基于 Maliit 以前的版本,那么需要按照这份迁移指南部分重写你的接口;如果你的项目基于 0.20.0 或者以后的版本,那么不受这项改动的影响。
为了与系统的主题、外观保持一致,并且能够自动处理虚拟键盘遮挡的问题,GUI 应用程序应当基于 meego-ux-components 控件库 (toolkit) 进行开发。meego-ux-components 是 MeeGo 官方项目,并且是 MeeGo 官方 API 的一部分。
详细的信息请参考:
对应用程序开发者来说,开发环境需要模拟或者完全重现 MeeGo 系统的环境,包括应用程序依赖的 库、守护程序和配置等。一般来说,可以将开发环境部署在以下位置之一:
本章将对上述3种情况逐一介绍
在普通的 Linux 桌面中搭建开发环境的大致思想是:将待开发的项目以及它的依赖项目都在 开发系统中编译安装,尽量模拟 MeeGo 系统的环境。这种做法的好处是
这种做法的短处是:
设置环境可能会相当复杂
编译运行一个普通的软件大致需要的依赖关系是 Qt, 以及 UI Components 库。Qt 可以从源代码编译安装(花费时间很长),或者使用桌面 Linux 发行版中的 Qt 软件包。需要安装的版本请参考 MeeGo 发行版中的版本。
MeeGo SDK 本质上是运行于 qemu 模拟器中的完整 MeeGo 系统。目前它和目标平台上的 MeeGo 系统完全一致,除了图形系统的配置有所区别。MeeGo SDK 所用的 qemu 模拟器是加入 opengl 加速补丁的特有软件。
这种做法的好处是
这种做法的短处是:
部署 MeeGo SDK 的具体步骤请参考 SDK/Docs/1.1/Getting started with the MeeGo SDK for Linux
这种做法的好处是
这种做法的短处是:
MeeGo 的软件项目大部分使用 Git 做版本控制。本章将介绍 Git 的使用技巧和底层的基本概念。 这些基本概念有助于软件开发者理解 Git 繁多的命令选项。
Git 存储数据的基本单位叫做 Blob。例如,单个文件的内容存储于一个 Blob 中。Git 使用 Blob 数据的 SHA1 hash 值(长度为40字符)来唯一地标识每个 Blob。一般在同一项目范围内,简单地使用 hash 值的前 7-8 个字符便可以唯一地确定完整的数据。
如果将 Blob 看作文件系统中的单个文件的话,Tree 就对应文件系统中的目录了。Tree 包含其中 Blob 和下一级 Tree 的引用,该引用当然以 SHA1 hash 值的形式存在。Tree 的内容(目录信息)也存在对应的 SHA1 hash。同样地被用作对 Tree 的引用。
每个 Commit 对应整个项目所有文件在某个时刻的完整快照。它包括基本信息(作者,提交者,日期,parent等)和整个项目 Tree 的引用。同样地,Commit 的这些信息也被计算出 hash 值,用作对 Commit 的标识。用户输入的绝大多数 Git 命令,在引用某个特定的项目版本时,就是在指定 Commit hash。
Commit 是各种 Git 操作的重要基本单位。每次项目变更提交后,都会形成一个新的 Commit,它的 parent 指针指向前一时刻的 Commit。(某些 Commit 可能会有多个 parent )。因此,项目仓库中的所有 Commit 的关系形成了一个 DAG (有向非循环图)。
在用 Git 管理的项目目录中,存在一个名为 .git 的目录。该目录中存放有 git 仓库数据,仓库中存放了所有项目文件的历史版本,即所有的 Blob,Tree 和 Commit。其他正常的供开发者编辑的项目文件叫做 working tree,通常存放从仓库中取出的某个 Commit。
Index (有时被称作 staging area) 是独立于仓库和 working tree 之外的空间。一般来说,在 working tree 中的多次改动会先后被提交到 Index,直到开发者满意后,这些改动才会被正式提交到仓库中。从某种意义上来说,Index 就像一份草稿,用来形成新的 Commit。
和其他版本管理系统类似,Branch 在 Git 中也承担类似的任务。在 Git 中,Branch 只是一个指向 Commit 的名字,并且在发生 Commit 之后会自动更新(指向新的 Commit)。在很多 Git 命令中,读者会看到使用 branch 名字作为参数,实际上是对相应 Commit 的操作。如果翻阅 Git 命令手册就会发现,很多 Git 命令实际上接受任何 Commit 的引用。
Rebase 是 Git 相对于其他版本管理系统独有的功能之一。用来将某个 Commit 和它所有的后续 Commit 移动到一个新的 Commit 上面。例如
假定某时刻仓库中有 A-G 7 个 Commit,关系如下所示,并且当前处于 topic 分支上。这种情况非常常见,例如 master 分支是 upstream 的代码,读者在 F 时刻开发了自己的代码,存放于 topic 分支中,并且做了 A B C 三次改动,这时,upstream 又发生了新的变动 G。读者想把自己的改动 "rebase" 到 upstream 新的代码上,仍然存放于 topic 分支中
A---B---C topic / D---E---F---G master
运行
git rebase master git rebase master topic
rebase 之后的仓库如图所示:
A'--B'--C' topic / D---E---F---G master
Reset 可用来"编辑" working tree 和 index 的状态,以及 HEAD 指针。reset 通常工作在三种模式下 (默认的mixed, soft, hard)。在 soft 模式下,working tree 和 index 的内容维持不变,只改变 HEAD 指针;在 hard 模式下,index 和 working tree 的内容会被重置到指定 commit,相应地修改会丢失;在默认的mixed 模式下,会重置 index,但不会改变 working tree。
将历史记录中某人的 email 和名字进行重命名:
git filter-branch -f --commit-filter ' \ if [ "$GIT_AUTHOR_EMAIL" = "roger.wang@intel.com" ]; \ then \ GIT_AUTHOR_NAME="Roger"; \ GIT_AUTHOR_EMAIL="roger.wang@gmail.com"; \ git commit-tree "$@"; \ else \ git commit-tree "$@"; \ fi' HEAD
本章将介绍 Linux 开发环境中编译软件的一些技巧。MeeGo 编译系统 OBS 的介绍不属于本章的 范围。
下列工具一般配合并行编译 make -j <n> 使用
编译大型工程的时候,经常会发生重新编译的情况,这些情况下 make 工具往往不能优化处理,例如
这通常会导致大量的文件被重复编译。ccache 工具就可以解决这一问题。它在同一文件和头文件以及编译选项不变的情况下,直接取出以前编译过并缓存的 .o 目标文件,大大节省了编译时间。
MeeGo 仓库中有 ccache 软件包,在开发系统中安装 ccache 后,用户只需要使用 ccache 自带的 gcc 命令进行编译即可。该 gcc 命令是一个包装程序,实际会调用系统中原有的 gcc 完成相应的功能。为了使用 ccache 自带的 gcc 命令,只需要将所在的目录加入 PATH 环境变量的开始即可。在 MeeGo 系统中这一步已经自动完成。
如前所述,ccache 会缓存目标文件,它的缓存一般位于 ~/.ccache 中,默认占用空间上限为 2GB。用户可以使用 ccache -s 查看空间使用情况和 cache 命中率。在我的系统中 ccache 的命中率大概是 50%,大概节约了几十万次编译!
distcc 和 icecream 都是远程编译工具,它们将源代码发送到其他机器上进行编译,并取回编译的目标文件。常常在开发环境的硬件条件有限的情况下使用(例如在目标平台下编译时,希望使用台式机的资源)。
distcc 的使用非常简单,安装 distcc 后,配置 /etc/distcc/hosts,每一行加入一个远程主机,格式为
主机名(或者IP)/并行作业上限
然后在远程主机上安装 distcc daemon。
和 ccache 一样,distcc 也作为 gcc 的壳,将它的壳程序所在目录加入 PATH 环境变量的开始即可。值得一提的是,distcc 可以和 ccache 联合使用。就是说代码如果没有命中 ccache 的缓存时,将被发送到远程主机编译,并将编译的结果缓存起来!联合使用的方法是:
值得注意的是,使用 distcc 编译时,可能会发送大量的源文件文本,所以网络带宽经常会成为瓶颈,所以请确保至少有百兆的网络连接。
icecream 和 distcc 项目非常相像,它是为了解决 distcc 负载均衡问题而诞生的项目。在有多个远程编译主机时,distcc 的负载可能会出现不均衡的情况。icecream 通过设置中央调度服务器解决了这个问题。icecream 的详细使用方法请参考 How to use icecream
如果在 Linux 桌面环境下进行开发,可能需要安装不少依赖的库,这些库通常又是直接从 upstream 下载代码,在本地编译安装的。随着开发不断地进行,对这些库的升级就成了一项麻烦而又不得不进行的工作。而应用程序和库、库和库的依赖关系往往又存在版本限制,维护这些依赖关系就成了头疼的问题。
Jhbuild 工具可以帮助解决这些问题。它从包括 CVS, Subversion, Git, Bazaar 和 tarballs 在内的各种软件源取得源代码,按照正确的依赖顺序编译安装。用户可以指定只编译哪些项目,以及从哪里开始继续编译。
编译过 GNOME 项目的人基本都使用过 jhbuild,但 jhbuild 的使用不限于 GNOME 项目。只要用户写好适合自己的配置文件就可以了。读者可以参考GNOME的入门资料
大部分的调试工具都基于 GDB。读者可以直接使用 GDB (推荐),或者使用 GDB 的“壳”工具,例如 DDD,gdb -tui命令,Emacs 中的 gud,或者使用 Eclipse 配合目标平台上的 gdbserver 进行远程调试。
本节列出 GDB 的常用操作,供初学者参考。限于篇幅的原因,本文不会对命令的具体用法做介绍,读者可直接参考 GDB 使用手册 中对应的部分,那里有很好的说明。
编译debug版本的程序:在编译程序时,指定编译参数 -g -O0,其中 -g 为生成调试信息(在 OBS 中使用该参数会生成正确的 debuginfo 包),-O0 为关闭优化。
指定程序参数:在 GDB 提示符下使用 setargs 命令或者启动 GDB 时使用 --args 参数。
运行:r, c, finish, n, kill。具体请参考 GDB 使用手册
断点:b, info breakpoints, disable, enable
查看程序状态:bt, up, down, print
加载 debuginfo:有时需要在 MeeGo 发布的 image 中进行调试,这时需要安装调试信息,首先通过软件包管理器安装对应软件的 debuginfo 软件包,然后用 gdb 运行对应的程序,就可以自动显示出对应的函数和变量名。debuginfo 包中的内容是在编译完成后将 -g 选项生成的那些调试信息剥离出来生成的数据。在原先的可执行文件中,会有专门的 .gnu_debuglink 区指向调试信息的文件名,并且配有校验值以确保版本一致。.gnu_debuglink 中的文件名指针并没有包含完整路径,默认情况下,GDB 会从 debug-file-directory 指定的目录中寻找。读者可以自己在 GDB 中查看对应的配置。
(gdb) b somefilename.cpp:1234 Breakpoint 2 at ... (gdb) command 2 > bt > cont > end (gdb) cont
(gdb) p creat("/tmp/somefile.dat", 0777)
$6 = 37
(gdb) p write(37, s, len)
$7 = 148
(gdb) p close(37)
$8 = 0
GDB 可以使用 python 语言编写扩展命令,例如,将下列配置放入 ~/.gdbinit 文件中,就拥有了专门打印 QString 类型的命令:
define printqstring
printf "(QString)0x%x (length=%i): \"",&$arg0,$arg0.d->size
set $i=0
while $i < $arg0.d->size
set $c=$arg0.d->data[$i++]
if $c < 32 || $c > 127
printf "\\u0x%04x", $c
else
printf "%c", (char)$c
end
end
printf "\"\n"
end
具体编写 python 脚本的方法请参考 Scripting gdb using Python
在前面搭建开发环境一章中提到过,在目标平台部署开发环境的限制之一是目标平台的硬件配置较低,有时会阻碍调试。所以,有时会使用 GDB 远程调试的功能。它的基本原理是在目标平台运行 gdbserver,该程序控制应用程序的运行、设置断点等。gdbserver 和开发平台上的 gdb 工具通信,给予开发者远程控制的功能。
启动 gdbserver 的方法和 GDB 类似,用 'gdbserver' 命令来代替 'gdb',并且要在第一个参数(程序路径)之前加入通讯信息参数。该参数指定了 gdbserver 的侦听端口。详细的方法请参考 Debugging Remote Programs
MeeGo 系统中大量使用 D-Bus 来实现各种服务,所以在调试时经常需要和 D-Bus 打交道。 本章介绍和 D-Bus 相关的调试技巧。
推荐阅读 MeeGo 官方 D-Bus 指导
D-Bus是一种 IPC 机制,底层实现基于 unix socket,向上层提供一种远程过程调用接口。
和一般的 Linux 桌面系统类似,MeeGo 中的存在 2 条 D-Bus:system bus 用来提供系统服务执行环境,而 session bus 用来进行和当前登录用户有关的应用服务。大部分 D-Bus 服务是在 session bus 上提供的。
在调试过程中,一般需要监视 D-Bus 消息,或者手动调用某个 D-Bus 接口,用来触发被调试的代码。
可以使用 dbus-monitor 监视 D-Bus 消息,该工具的示例用法请见 dbus-monitor (command line)
值得注意的是,出于安全考虑,默认情况下 system bus 不允许监视,读者可以通过如下配置来绕过这一限制:
cat > /etc/dbus-1/system-local.conf
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"[http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd]">
<busconfig>
<policy context="default">
<!-- All messages may be received by default -->
<allow receive_requested_reply="false" receive_type="method_call" eavesdrop="true"/>
<allow receive_requested_reply="false" receive_type="method_return" eavesdrop="true"/>
<allow receive_requested_reply="false" receive_type="error" eavesdrop="true"/>
<allow receive_requested_reply="false" receive_type="signal" eavesdrop="true"/>
<allow eavesdrop="true"/>
</policy>
<policy user="root">
<allow send_destination="*" eavesdrop="true"/>
<allow receive_sender="*" eavesdrop="true"/>
</policy>
</busconfig>
查询,调用指定 D-Bus 接口推荐使用图形节目的 DFeet, 或者命令行界面的 dbus-send, qdbus。示例用法请参考 MeeGo 官方 D-Bus 指导
$ dbus-send --session --print-reply --reply-timeout=2000 \ --type=method_call --dest=org.freedesktop.DBus /org/freedesktop/DBus \ org.freedesktop.DBus.ListActivatableNames
在开发过程中,经常需要通过 SSH 登录到远程 MeeGo 系统中,运行程序并调试,但是由于 SSH 登录后的环境变量设置和图形环境下有出入,使得很多依赖 D-Bus 的程序无法正常工作。一般可以通过设置正确的 DBUS_SESSION_BUS_ADDRESS 环境变量解决。但是该环境变量每次启动图形界面后都会变动,所以手动设置会很麻烦,下面的 bash 配置可以自动完成这一工作。将下面一行文本添加至 ~/.bashrc:
alias getdbus='export DBUS_SESSION_BUS_ADDRESS=`cat /proc/$(pidof mcompositor)/environ | tr "\0" "\n" | grep DBUS_SESSION_BUS_ADDRESS | cut -d "=" -f2-`'
然后在 SSH 登录后运行 getdbus 即可。注意也需要设置 DISPLAY 环境变量为 :0.0
对于稍微复杂一些的 D-Bus 测试,可以使用 python 脚本来编写。dbus-python 的 API 可以参考 dbus-python tutorial
下面是一段简单的 python 脚本,用ofono 框架呼叫电话:
#!/usr/bin/python
import sys
import dbus
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object('org.ofono', '/'),
'org.ofono.Manager')
modems = manager.GetModems()
path, properties = modems[0]
manager = dbus.Interface(bus.get_object('org.ofono', path),
'org.ofono.VoiceCallManager')
if len(sys.argv) > 2:
path = manager.Dial(sys.argv[1], sys.argv[2])
else:
path = manager.Dial(sys.argv[1], "")
print path
MeeGo 系统中可以使用 Linux 平台下的各种性能工具。读者可以使用自己熟悉的工具。本章介绍一些基本的 Linux 性能工具。
若需要查看系统的整体状况,可以用一些常用工具,例如:
1160 ? S 0:00 upstart-udev-bridge --daemon 1162 ? S<s 0:00 udevd --daemon 1331 ? S< 0:00 \_ udevd --daemon 1333 ? S< 0:00 \_ udevd --daemon 1540 ? Ss 0:00 dd bs=1 if=/proc/kmsg of=/var/run/rsyslog/kmsg 1548 ? Sl 0:16 rsyslogd -c4 1592 ? Ss 1:01 dbus-daemon --system --fork 1600 ? Ss 0:55 avahi-daemon: running [powerbuilder.local] 1601 ? Ss 0:00 \_ avahi-daemon: chroot helper 1608 ? Ssl 0:05 hald --daemon=yes 1747 ? S 0:00 \_ hald-runner 2122 ? S 0:00 \_ hald-addon-input: Listening on /dev/event0 /dev/ev 2123 ? S 0:00 \_ /usr/lib/hal/hald-addon-cpufreq 2124 ? S 0:00 \_ hald-addon-acpi: listening on acpid socket /var/ru 2128 ? S 0:02 \_ hald-addon-storage: polling /dev/sr0 (every 2 sec) 1609 ? Ssl 0:46 gdm-binary 1866 ? Sl 0:00 \_ /usr/lib/gdm/gdm-simple-slave --display-id /org/gnome/ 2129 tty7 Ss+ 1:19 \_ /usr/bin/X :0 -br -verbose -auth /var/run/gdm/auth 2511 ? Sl 0:00 \_ /usr/lib/gdm/gdm-session-worker 2533 ? Ssl 0:01 \_ gnome-session 2623 ? Ss 0:03 \_ /usr/bin/ssh-agent /usr/bin/dbus-launch -- 2645 ? Ss 0:00 \_ /usr/bin/seahorse-agent --execute gnome-se 2711 ? S 0:11 \_ /usr/bin/metacity --replace 2735 ? S 48:45 \_ gnome-panel
powertop 是分析软件耗电情况的强大工具。它甚至可以帮开发者追踪到 CPU 是为何被从睡眠状态唤醒。该软件的界面类似 top 工具。
powertop 由 Intel 的工程师开发,是 IA 平台上分析耗电性能的最好工具之一。请参考 powertop 的详细介绍和用法。
perf 是继 oprofile 之后,由 Linux Kernel 项目提供的下一代强大的性能分析工具。
它的使用方法很简单,安装 perf 后,只需要在程序的前面启动 perf:
$ sudo perf record -f -- du -sh /usr/local 1.1G /usr/local [ perf record: Woken up 1 times to write data ] [ perf record: Captured and wrote 0.034 MB perf.data (~1496 samples) ]
程序运行完成后,用 perf 统计收集到的性能数据:
$ sudo perf report --sort comm,dso,symbol | head -10
Failed to open /usr/bin/du, continuing without symbols
# Events: 439 cycles
#
# Overhead Command Shared Object Symbol
# ........ ........... ................. ......................................
#
11.90% du [kernel.kallsyms] [k] _raw_spin_lock_irqsave
9.41% du [kernel.kallsyms] [k] ifind_fast
6.77% du [kernel.kallsyms] [k] __d_lookup
4.06% du [kernel.kallsyms] [k] __wake_up_bit
3.31% du libc-2.11.2.so [.] _int_free
perf 的好处之一是它可以同时查看应用层程序和 kernel 级别的代码性能状况。便于联合多种信息解决问题。
有关 perf 的更多信息和用法请参考perf 官方网站。
valgrind 常常用来侦测内存泄漏和指针越界访问。由于它将程序放在自身的虚拟机中执行,所以它可以拦截任何非正常的指针访问。该特性也导致了程序在 valgrind 中运行速度大大减慢,运行时间可能会增长数十倍之多。
使用 valgrind 的方法也非常简单,用 valgrind 运行你的程序即可。它的输出类似下图所示: (用 valgrind 运行 date)
$ valgrind date ==4732== Memcheck, a memory error detector ==4732== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al. ==4732== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info ==4732== Command: date ==4732== Wed Mar 23 09:08:56 CST 2011 ==4732== ==4732== HEAP SUMMARY: ==4732== in use at exit: 0 bytes in 0 blocks ==4732== total heap usage: 53 allocs, 53 frees, 4,605 bytes allocated ==4732== ==4732== All heap blocks were freed -- no leaks are possible ==4732== ==4732== For counts of detected and suppressed errors, rerun with: -v ==4732== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 16 from 7)
详细信息请参考 valgrind 官方网站。
systemtap 可以说是 Linux 平台上的 dtrace。它提供强大灵活的脚本机制,开发者可以在指定的目标代码处插入脚本,收集感兴趣的性能数据。
例如,下面的脚本可以统计程序 ls 中每个函数的调用参数和返回值:
probe process("/bin/ls").function("*").call
{
printf("called %s(%s)\n", probefunc(), $$parms);
}
probe process("/bin/ls").function("*").return
{
printf("returned %s:%s\n", probefunc(), $$return);
}
目前 systemtap 还没有进入 Linux Kernel 项目,用户需要自己给内核代码打入对应的 systemtap 补丁并重新编译,所以安装相对比较麻烦。
这里推荐 systemtap 更详细的介绍
不正确的系统时间可能会引起以下问题: