我们知道Ubuntu平台提供了良好的融合(convergence)设计.通过融合设计,使得我们的同样一个应用在不需要修改任何代码的情况下,重新打包就可以运行到不同的屏幕尺寸的设备上.当然,Canonical公司最终的目的是实现snap应用运行到所有设备上,而不需要进行任何的重新打包的动作.目前Ubuntu手机上支持的应用打包格式是click包.在为了的Ubuntu SDK中,最终我们会把snap的支持加入到我们的SDK之中去.那么目前我们怎么把我们已经开发好的应用打包成为一个snap应用包,并可以成功部署到我们的电脑桌面(16.04)上呢?
如果大家对如何安装一个snap应用到16.04的桌面系统上的话,请参阅文章"安装snap应用到Ubuntu 16.4桌面系统".
1)通过Ubuntu SDK开发一个我们需要的手机应用
我们可以通过Ubuntu SDK来创建一个我们想要的项目.关于如何创建一个Ubuntu手机应用,这个不在我们的这个教程范围.如果你对如何利用Ubuntu SDK开发一个手机应用感兴趣的话,请参考我们的文章"
Ubuntu 手机开发培训准备".这里将不再累述!
值得指出的是:在今天的教程中,我们将教大家如何把一个qmake的Ubuntu手机应用打包为一个snap的应用.这里,我们将利用之前我已经开发的一个项目作为例程来开始.我们在terminal下打入如下的命令:
$ git clone https://github.com/liu-xiao-guo/rssreader_snap
下载后的源码结构如下:
liuxg@liuxg:~/snappy/desktop/rssreader$ tree -L 2
.
├── snapcraft.yaml
└── src
├── manifest.json.in
├── po
├── rssreader
└── rssreader.pro
从上面的结构上,我们可以看到:在src目录下的整个项目是有我们的Ubuntu SDK所创建的一个qmake项目,它有一个项目文件.pro文件.在手机上的运行情况如下:
同时,在我们项目的根目录下,我们发现了另外一个文件snapcraft.yaml.这个文件就是为了能够让我们把我们的qmake项目最终打包为snap应用的snap项目文件.
2)为我们的qmake项目打包
在上节中,我们已经提到了我们项目的snapcraft.yaml文件.现在我们把这个文件展示如下:
snapcraft.yaml
name: rssreader-app
version: 1.0
summary: A snap app from Ubuntu phone app
description: This is an exmaple showing how to convert a Ubuntu phone app to a desktop snap app
confinement: strict
apps:
rssreader:
command: desktop-launch $SNAP/lib/x86_64-linux-gnu/bin/rssreader
plugs: [network,network-bind,network-manager,home,unity7,opengl]
parts:
rssreader:
source: src/
plugin: qmake
qt-version: qt5
build-packages:
- cmake
- gettext
- intltool
- ubuntu-touch-sounds
- suru-icon-theme
- qml-module-qttest
- qml-module-qtsysteminfo
- qml-module-qt-labs-settings
- qtdeclarative5-u1db1.0
- qtdeclarative5-qtmultimedia-plugin
- qtdeclarative5-qtpositioning-plugin
- qtdeclarative5-ubuntu-content1
- qt5-default
- qtbase5-dev
- qtdeclarative5-dev
- qtdeclarative5-dev-tools
- qtdeclarative5-folderlistmodel-plugin
- qtdeclarative5-ubuntu-ui-toolkit-plugin
- xvfb
stage-packages:
- ubuntu-sdk-libs
- qtubuntu-desktop
- qml-module-qtsysteminfo
- ubuntu-defaults-zh-cn
stage:
- -usr/share/pkgconfig/xkeyboard-config.pc
snap:
- -usr/share/doc
- -usr/include
after: [desktop/qt5]
初以乍看,这个文件和我们以往所看到的文件都不同.似乎很复杂!关于snapcraft.yaml的具体解释,我们可以参考我们的官方文档"
snapcraft.yaml syntax".
在这里,我们做一个简单的解释:
- name: 这是最终的包的名称.针对我们的情况,我们最终的snap包的名字为rssreader-app_1.0_amd64.snap
- version: 这是我们包的版本信息.就像我们包的名称rssreader-app_1.0_amd64.snap所展示的那样.1.0是我们的版本信息
- summary: 这是一个描述我们包信息的字符串.根据我们的设计,他只能最多长达79个字符
- description:这是一个描述我们包的字符串.它可以比summary来得更长一些
- confinement: 受限的种类:strict 或 devmode.当我们设置为devmode时,在安装时加上--devmode选项时,可以使得我们的应用不接受任何的安全的限制.就像我们以前在Ubuntu电脑上开发一样.我们可以随意地访问任何一个我们想要访问的目录等等
- apps: 在这里定义我们的应用及其运行时所需要的命令.针对我们的情况,我们定义rssreader为我们的应用.当我们执行我们的应用时,我们需要使用<<包名>>.<<应用名>>来运行我们的应用.针对我们的情况,我们使用rssreader-app.rssreader来通过命令行来运行我们的应用
- command:这是用来启动我们应用所需要的命令行.针对我们的情况:desktop-launch $SNAP/lib/x86_64-linux-gnu/bin/rssreader.这里的desktop-launch来自我们下面的已经预先编译好的包 desktop/qt5
- plugs:这一项定义了我们的snap应用所访问的权限.通过我们的设定,我们可以访问系统的$HOME目录及使用opengl等.更多细节请擦参阅Interfaces
- parts: 每个part定义了我们软件所需要的部分.每个part就像一个mini的小项目.在我们编译时可以在parts的目录中分别找到对应的部分
- rssreader: 我们定义的part的名称.它的名字可以是任何你所喜欢的名称
- source: 定义part的源码.它可以在网站上的任何一个软件(bzr, git, tar)
- plugin: 定义编译part所需要用到的plugin.开发者也可以拓展snapcraft的plugin.请参阅文章"Write your own plugins"
- qt-version:这个是针对Qt plugin来说的.定义Qt的版本
- build-packages:定义在这里的每个包都是为了用来编译我们的项目的.需要安装的.它们将不会出现在最终的snap文件中
- stage-packages:这些定义的包都要最终被打入到snap包中,并形成运行该应用所需要的文件.特别值得指出的是:我们加入了中文包ubuntu-defaults-zh-cn,从而使得我们的应用可以看见中文的显示.当然,我们包的大小也从100多兆增加到300多兆.注意这里的包都对应在我们通常ubuntu下的debian包
- snap:定义了我们需要的或不需要的文件.在这里我们通过"-"来把一些不想要的文件剔除从而不打入到我们的包中
- after:表明我们的这个part的编译必须是在desktop/qt5下载之后.对于有些项目,我们必须先得到一个part,并使用这个part来编译我们其它的part.在这种情况下,我们可以使用after来表明我们的先后顺序.在这里,我们也利用了其它人所开发的part desktop/qt5.我们可以在网址https://wiki.ubuntu.com/snapcraft/parts找到别人已经发布的parts.这些parts的描述也可以在地址https://wiki.ubuntu.com/Snappy/Parts找到.我们可以重复利用它们.在命令行中,我们也可以公共如下的命令来查找已经有的parts:
- snapcraft update
- snapcraft search
- rssreader: 我们定义的part的名称.它的名字可以是任何你所喜欢的名称
3)编译我们的snap应用
为了编译我们的snap应用,其实非常简单.我们直接进入到我们的项目的根目录下,并打入如下的命令:
$ snapcraft
312M 7月 13 12:25 rssreader-app_1.0_amd64.snap
就像我们上节中介绍的那样,由于我们加入了中文字体,所以我们的应用变得非常庞大.没有字体的snap包大约为141M.
如果我们想要清楚我们的打包过程中的中间文件,我们可以打入如下的命令:
$ snapcraft clean
它将清除在parts, stage及prime目录下的所有的文件.更多关于snapcraft的介绍,可以参阅我的文章" 安装snap应用到Ubuntu 16.4桌面系统".我们也可以通过如下的方式来得到它的帮助:
$ snapcraft --help
4)安装及运行我们的应用
我们可以通过如下的命令来安装我们的.snap文件:
$ sudo snap install rssreader-app_1.0_amd64.snap --force-dangerous
我们可以通过如下的命令来运行我们的应用:
$ rssreader-app.rssreader
运行时的画面如下:
5)安全调试
我们可以在我们的桌面系统中安装如下的软件:
$ snap install snappy-debug
如果在安装的过程中提示还需要安装其它的应用软件,我们按照提示安装即可.
接下来我们在一个terminal中打入如下的命令:
$ sudo snap connect snappy-debug:log-observe ubuntu-core:log-observe
这种方法也适用于我们建立其它需要手动链接的的情况.然后在另外一个terminal中打入如下的命令:
$ rssreader-app.rssreader
那么我们可以在这个窗口看到如下的信息:
显然在我们的应用运行时产生了一些安全的问题,并提示一些上面的输出信息.我们切换到另外一个运行命令"snappy-debug.security scanlog"的窗口:
显然在这个输出窗口也显示了一些"DENIED"安全错误信息.那么这些问题是怎么来的呢?显然,这可能是我们的应用没有设置相应的plug所致.我们可以参阅我们的官方文档
interfaces,并结合我们的应用.我们可以初步判断,我们可能需要network及network-bind plug.这是因为我们的应用是一个网路的应用,需要从网上抓数据.另外,在我们代码的
main.cpp中,我们使用了如下的代码:
QNetworkAccessManager *MyNetworkAccessManagerFactory::create(QObject *parent)
{
QNetworkAccessManager *nam = new QNetworkAccessManager(parent);
QString path = getCachePath();
QNetworkDiskCache* cache = new QNetworkDiskCache(parent);
cache->setCacheDirectory(path);
nam->setCache(cache);
return nam;
}
这段代码是为了设置一个cache,从而使得我们抓过来的照片能够得到cache,进而我们不需要浪费网路资源重复获取同样的一个照片.根据我们目前所有的plugs:
liuxg@liuxg:~$ snap interfaces
Slot Plug
:camera -
:cups-control -
:firewall-control -
:gsettings -
:home rssreader-app
:locale-control -
:log-observe snappy-debug
:modem-manager -
:mount-observe -
:network -
:network-bind -
:network-control -
:network-manager -
:network-observe -
:opengl rssreader-app
:optical-drive -
:ppp -
:pulseaudio -
:snapd-control -
:system-observe -
:timeserver-control -
:timezone-control -
:unity7 rssreader-app
:x11 -
显然,netwrok-manager是我们所需要的plug.我们可以在我们的snapcraft.yaml加入上面所述的两个plug.修改过后的snapcraft.yaml文件如下:
snapcraft.yaml
name: rssreader-app
version: 1.0
summary: A snap app from Ubuntu phone app
description: This is an exmaple showing how to convert a Ubuntu phone app to a desktop snap app
confinement: strict
apps:
rssreader:
command: desktop-launch $SNAP/lib/x86_64-linux-gnu/bin/rssreader
plugs: [network,network-bind,network-manager,home,unity7,opengl]
parts:
rssreader:
source: src/
plugin: qmake
qt-version: qt5
build-packages:
- cmake
- gettext
- intltool
- ubuntu-touch-sounds
- suru-icon-theme
- qml-module-qttest
- qml-module-qtsysteminfo
- qml-module-qt-labs-settings
- qtdeclarative5-u1db1.0
- qtdeclarative5-qtmultimedia-plugin
- qtdeclarative5-qtpositioning-plugin
- qtdeclarative5-ubuntu-content1
- qt5-default
- qtbase5-dev
- qtdeclarative5-dev
- qtdeclarative5-dev-tools
- qtdeclarative5-folderlistmodel-plugin
- qtdeclarative5-ubuntu-ui-toolkit-plugin
- xvfb
stage-packages:
- ubuntu-sdk-libs
- qtubuntu-desktop
- qml-module-qtsysteminfo
- ubuntu-defaults-zh-cn
snap:
- -usr/share/doc
- -usr/include
after: [desktop/qt5]
重新打包我们的应用并安装.我们可以利用:
$ snap interfaces
来显示我们应用所有的plug:
liuxg@liuxg:~/snappy/desktop/rssreader$ snap interfaces
Slot Plug
:camera -
:cups-control -
:firewall-control -
:gsettings -
:home rssreader-app
:locale-control -
:log-observe snappy-debug
:modem-manager -
:mount-observe -
:network rssreader-app
:network-bind rssreader-app
:network-control -
:network-manager -
:network-observe -
:opengl rssreader-app
:optical-drive -
:ppp -
:pulseaudio -
:snapd-control -
:system-observe -
:timeserver-control -
:timezone-control -
:unity7 rssreader-app
:x11 -
- rssreader-app:network-manager
在上面的最后一行,我们可以看到:
- rssreader-app:network-manager
这是什么意思呢?我们来重新查看连接
interfaces,在那个页面里虽然目前还没有network-manager的介绍,但是我们可以看到诸如:
network-control
Can configure networking. This is restricted because it gives wide, privileged access to networking and should only be used with trusted apps.
Usage: reserved Auto-Connect: no
liuxg@liuxg:~$ snap --help
Usage:
snap [OPTIONS] <command>
The snap tool interacts with the snapd daemon to control the snappy software platform.
Application Options:
--version print the version and exit
Help Options:
-h, --help Show this help message
Available commands:
abort Abort a pending change
ack Adds an assertion to the system
change List a change's tasks
changes List system changes
connect Connects a plug to a slot
create-user Creates a local system user
disconnect Disconnects a plug from a slot
find Finds packages to install
help Help
install Install a snap to the system
interfaces Lists interfaces in the system
known Shows known assertions of the provided type
list List installed snaps
login Authenticates on snapd and the store
logout Log out of the store
refresh Refresh a snap in the system
remove Remove a snap from the system
run Run the given snap command
try Try an unpacked snap in the system
liuxg@liuxg:~$ snap connect -h
Usage:
snap [OPTIONS] connect <snap>:<plug> <snap>:<slot>
The connect command connects a plug to a slot.
It may be called in the following ways:
$ snap connect <snap>:<plug> <snap>:<slot>
Connects the specific plug to the specific slot.
$ snap connect <snap>:<plug> <snap>
Connects the specific plug to the only slot in the provided snap that matches
the connected interface. If more than one potential slot exists, the command
fails.
$ snap connect <plug> <snap>[:<slot>]
Without a name for the snap offering the plug, the plug name is looked at in
the gadget snap, the kernel snap, and then the os snap, in that order. The
first of these snaps that has a matching plug name is used and the command
proceeds as above.
Application Options:
--version print the version and exit
Help Options:
-h, --help Show this help message
显然我们需要使用如下的命令来完成我们的connect工作:
$ snap connect <plug> <snap>[:<slot>]
$ sudo snap connect rssreader-app:network-manager ubuntu-core:network-manager
liuxg@liuxg:~$ snap interfaces
Slot Plug
:camera -
:cups-control -
:firewall-control -
:gsettings -
:home rssreader-app
:locale-control -
:log-observe snappy-debug
:modem-manager -
:mount-observe -
:network rssreader-app
:network-bind rssreader-app
:network-control -
:network-manager rssreader-app
:network-observe -
:opengl rssreader-app
:optical-drive -
:ppp -
:pulseaudio -
:snapd-control -
:system-observe -
:timeserver-control -
:timezone-control -
:unity7 rssreader-app
:x11 -
显然这一次,我们已经成功地把network-manager加入到我们的应用中.我们可以重新运行我们的应用:
liuxg@liuxg:~/snappy/desktop/rssreader$ rssreader-app.rssreader
(process:9770): Gtk-WARNING **: Locale not supported by C library.
Using the fallback 'C' locale.
Gtk-Message: Failed to load module "overlay-scrollbar"
Gtk-Message: Failed to load module "gail"
Gtk-Message: Failed to load module "atk-bridge"
Gtk-Message: Failed to load module "unity-gtk-module"
Gtk-Message: Failed to load module "canberra-gtk-module"
qml: columns: 1
qml: columns: 2
qml: currentIndex: 0
qml: index: 0
qml: adding page...
qml: sourcePage must be added to the view to add new page.
qml: going to add the page
qml: it is added: 958
XmbTextListToTextProperty result code -2
XmbTextListToTextProperty result code -2
显然我们的应用再也没有相应的错误提示了.
6)为我们的应用加上应用图标
到目前我们的应用虽然接近完美,但是我们还是不能够在我们的dash中启动我们的应用.为此,我们在我们的应用的根目录下创建了一个setup/gui目录.在该目录下,我们创建如下的两个文件:
liuxg@liuxg:~/snappy/desktop/rssreader/setup/gui$ ls -l
total 100
-rw-rw-r-- 1 liuxg liuxg 325 7月 14 13:18 rssreader.desktop
-rw-rw-r-- 1 liuxg liuxg 91353 7月 14 11:50 rssreader.png
liuxg@liuxg:~/snappy/desktop/rssreader$ tree -L 3
.
├── setup
│ └── gui
│ ├── rssreader.desktop
│ └── rssreader.png
├── snapcraft.yaml
└── src
├── manifest.json.in
├── po
│ └── rssreader.liu-xiao-guo.pot
├── rssreader
│ ├── components
│ ├── main.cpp
│ ├── Main.qml
│ ├── rssreader.apparmor
│ ├── rssreader.desktop
│ ├── rssreader.png
│ ├── rssreader.pro
│ ├── rssreader.qrc
│ └── tests
└── rssreader.pro
有了上面的两个文件,我们再次重新打包并安装我们的snap应用.安装完后,在我们的dash里:
我们可以找到我们的RSS Reader应用了.
7)在不需要安装的情况下运行我们的应用
我们知道在开发的过程中,我们每次安装的时候都会产生一个新的版本.这个新的安装不仅花费很多的时间,而且占用系统的空间(每个版本占用的空间比较大).在开发时,我们能否不用安装而直接使用已经在打包过程中生产的文件呢?答案是肯定的.我们可以通过:
liuxg@liuxg:~$ snap --help
Usage:
snap [OPTIONS] <command>
The snap tool interacts with the snapd daemon to control the snappy software platform.
Application Options:
--version print the version and exit
Help Options:
-h, --help Show this help message
Available commands:
abort Abort a pending change
ack Adds an assertion to the system
change List a change's tasks
changes List system changes
connect Connects a plug to a slot
create-user Creates a local system user
disconnect Disconnects a plug from a slot
find Finds packages to install
help Help
install Install a snap to the system
interfaces Lists interfaces in the system
known Shows known assertions of the provided type
list List installed snaps
login Authenticates on snapd and the store
logout Log out of the store
refresh Refresh a snap in the system
remove Remove a snap from the system
run Run the given snap command
try Try an unpacked snap in the system
我们在上面的命令中,发现一个叫做 try的命令.在我们运行完snapcraft命令后,snapcraft会帮我们生产相应的.snap文件.同时它也帮我们生产如下的目录:
liuxg@liuxg:~/snappy/desktop/rssreader$ ls -d */
parts/ prime/ setup/ src/ stage/
$ sudo snap try prime/
liuxg@liuxg:/var/lib/snapd/snaps$ ls -al
total 287200
drwxr-xr-x 2 root root 4096 7月 15 11:13 .
drwxr-xr-x 7 root root 4096 7月 15 11:13 ..
-rw------- 1 root root 98439168 7月 14 13:10 mpv_x1.snap
-rw------- 1 root root 127705088 7月 14 17:30 photos-app_x1.snap
lrwxrwxrwx 1 root root 42 7月 15 11:13 rssreader-app_x1.snap -> /home/liuxg/snappy/desktop/rssreader/prime
-rw------- 1 root root 16384 7月 13 15:53 snappy-debug_22.snap
-rw------- 1 root root 67899392 7月 13 12:26 ubuntu-core_122.snap
我们看见其实就是一个软链接.通过这样的方法,我们很快地部署了我们的snap应用.同时避免每次安装时各个不同版本之间带来的不同安装.snap包实际上是一个使用squashfs打包而生产的.通过snap try,我们不需要创建一个squashfs文件.他的另外一个好处是我们可以随时修改这个snap的应用的内容,这是因为它本身是read-write的.我们可以甚至加上--devmode选项来取消应用对安全的限制.
8)利用devmode免除在开发时的安全考虑
我们知道在开发的过程中,我们有时不知道需要使用哪写plug来保证我们的软件正常运行.这个时候,我们可以在我们的snapcraft中定义修改我们的confinement使之成为devmode:
snapcraft.yaml
name: rssreader-app
version: 1.0
summary: A snap app from Ubuntu phone app
description: This is an exmaple showing how to convert a Ubuntu phone app to a desktop snap app
confinement: devmode
apps:
rssreader:
command: desktop-launch $SNAP/lib/x86_64-linux-gnu/bin/rssreader
plugs: [network,network-bind,network-manager,home,unity7,opengl]
parts:
rssreader:
source: src/
plugin: qmake
qt-version: qt5
build-packages:
- cmake
- gettext
- intltool
- ubuntu-touch-sounds
- suru-icon-theme
- qml-module-qttest
- qml-module-qtsysteminfo
- qml-module-qt-labs-settings
- qtdeclarative5-u1db1.0
- qtdeclarative5-qtmultimedia-plugin
- qtdeclarative5-qtpositioning-plugin
- qtdeclarative5-ubuntu-content1
- qt5-default
- qtbase5-dev
- qtdeclarative5-dev
- qtdeclarative5-dev-tools
- qtdeclarative5-folderlistmodel-plugin
- qtdeclarative5-ubuntu-ui-toolkit-plugin
- xvfb
stage-packages:
- ubuntu-sdk-libs
- qtubuntu-desktop
- qml-module-qtsysteminfo
- ubuntu-defaults-zh-cn
snap:
- -usr/share/doc
- -usr/include
after: [desktop/qt5]
confinement: devmode
当我们这样设计后,plugs里定义的所有的plug将不起任何的作用(我们甚至可以把它们全部删除).在我们安装我们的snap时,我们使用如下的命令:
$ sudo snap install rssreader-app_1.0_amd64.snap --devmode