zoukankan      html  css  js  c++  java
  • [转]使用 Python 实现跨平台的安装程序

    王 欣鹏, 软件工程师, IBM
    董 英博, 软件工程师, IBM

    简介: 本文通过对 Unix 平台的".bin"安装程序的结构和工作原理进行分析,提出了一种使用 Python 实现跨 Windows 和 Unix 平台安装程序的方法,并提供了简单的实现。

    引言

    我们在使用类 Unix 系统时,经常会用到一些以“.bin”或者“.run”结尾的安装程序 (Installer)。(为描述方便,这里我们使用“Bin 安装程序”来泛指这种安装程序。)Bin 安装程序不依赖于系统发行版自己的包 (package) 管理器来实现应用程序的安装和卸载,而是完全自己控制安装的整个过程,程序卸载的时候需要用户执行应用程序安装目录下的卸载脚本来完成。

    Bin 安装程序最大的好处就是可以运行在多种类 Unix 平台,以及基于相同核心的多个发行版上,而不需要关心系统使用何种包管理器。在一定程度上实现了跨平台。

    但是,非常遗憾的是,这种安装程序不能用在 Windows 平台上。Windows 平台上的安装程序需要特别的制作。这个是由 Bin 安装程序本身使用类 Unix 平台通用的 Shell 脚本来实现整个安装过程的引导和控制造成的。

    好消息是,现在我们有了一种强大的,可以同时运行在类 Unix 平台和 Windows 平台的脚本 -Python。随着 Python 的普及,越来越多的系统整合开始基于 Python 来完成,使得现在很大一部分类 Unix 平台都默认部署了 Python 的运行环境。即使没有默认安装,用户在安装其它应用的时候可能也都安装了 Python。

    所以,我们能不能使用 Python 来实现一个可以在类 Unix 平台和 Windows 平台都通用的安装程序呢?这个安装程序又如何来实现呢?本文将和大家探讨这个问题,并提供一个解决这个问题的思路。本文抛开被安装程序的跨平台能力,仅讨论安装程序本身的跨平台特性和实现方法。

    本文首先分析了 Bin 安装程序的结构和工作原理 , 然后介绍如何应用 Python 实现类似功能 , 并对 Python 实现的局限性以及可能的解决方案进行了探讨。

     

    Bin 安装程序的执行过程

    Bin 安装程序在运行的最初阶段会提供一些向导界面 , 向用户提供关于被安装产品的相关信息 , 并引导用户输入安装程序需要的配置信息 . 安装程序获取了需要的配置信息后 , 进入具体的安装阶段。

    在安装阶段 , Bin 安装程序首先展开一些包,这些包中包含一些安装程序自身所依赖的库和安装程序的配置信息,以及将要安装的用户应用程序。

    事实上,这些包被展开的过程分为两个阶段。在第一个阶段,安装程序将这些包的压缩文件从安装程序自身中分离出来,生成单独的压缩文件。然后在第二阶段,使用解压缩工具将这些压缩文件解压,并设置需要的系统环境变量。最基本的是 PATH 和 JAVA_HOME 这两个变量。

    在这之后,安装程序读取安装配置,取得用户自定义的安装脚本(pre-install, post-install),按照既定的顺序执行它们。

    最后,安装程序将安装过程中记录下来的被安装的文件列表和卸载程序模板,以及用户自定义的卸载脚本(pre-uninstall, post-uninstall)合并,生成卸载程序,并放入用户应用程序的安装目录。

     

    Bin 安装程序的实现分析

    那么,Bin 安装程序是如何实现这整个过程的控制的,安装程序文件本身又是一个什么样的结构呢?我们接下来进行分析。

    Bin 安装程序本身其实是一个包含二进制数据的 Shell 脚本。简单的安装程序可以由一段脚本代码加上一个压缩包的二进制数据构成。复杂的可能会包含多段二进制数据。

    文件的基本结构如下图


    图示 1:Bin 文件结构示意图
    图片示例 

    知道了文件结构之后,我们就要看看安装程序是怎么将自身文件内部的二进制数据分离出来了。其实这个有很多种方法实现。最基本的方法可以使用 tail 命令,或者使用 sed 命令。复杂些的可以使用读取文件的方法。

    接下来,我们用一个小范例来演示这个过程。

    首先,随便找些文件放到一个文件夹中,假设这个文件夹叫“app”。

    然后,使用压缩工具将这个文件夹压缩成“app.tar.gz”文件。

    接下来,我们准备一个简单的安装脚本。这个脚本仅仅将二进制文件分离出来,并在当前目录下解压。


    清单 1:安装脚本示例

    				
     #!/usr/bin/env bash 
    
     echo 'Spliting binary data to app.tar.gz ...'
     sed -n -e '1,/^BINARY$!p' $0 > 'app.tar.gz'
    
     if [ -f “app.tar.gz” ]; then 
        echo 'Extracting app.tar.gz ...'
        tar xvf 'app.tar.gz' >/dev/null 2>&1 
     fi 
    
     echo “Finiahed”
     exit 0 
     BINARY

    将这个脚本保存为 install.sh。

    最后,我们把之前做好的 app.tar.gz 文件和这个脚本合并,生成安装程序


    清单 2:合并脚本和 bin 文件

    				
     $ cat install.sh app.tar.gz > app.bin

    这样这个 Bin 安装程序就做好了。然后可以通过如下命令来测试这个安装程序的执行情况。


    清单 3:执行 app.bin

    				
     $ chmod u+x app.bin 
     $ ./app.bin

    如果一切正常的话,将会看到如下显示,并且当前目录下会出现“app.tar.gz”和名字为“app”的文件夹。


    清单 4:命令行输出

    				
     Spliting binary data to app.tar.gz ... 
     Extracting app.tar.gz …
     Finished

    这样一来,Bin 安装程序最核心的问题就解决了,接下来,只需要在脚本中添加设置环境变量,读取配置文件,加载依赖库,执行安装拷贝过程就可以了。

     

    使用 Python 实现跨平台的安装程序

    根据上面的分析,我们知道,我们至少要在这个安装程序中包含两部分内容:

    • 使用 Python 实现的安装控制脚本
    • 压缩包的二进制内容

    问题来了。Python 文件中不能直接包含二进制数据。Python 的编译过程会将二进制数据中的一些内容误认为是一些奇怪的文字而产生编译错误。

    那么我们该如何将压缩包放入 Python 的脚本呢?

    其实,我们可以使用 Base64 编码。这种编码将二进制数据转换成使用 ASCII 字符表示的文本信息。而我们只要将这些文本信息作为字符串的内容保存在 Python 脚本中就可以了。

    Python 在它的标准安装包中提供了这样的编码库。这样就不会产生需要额外依赖库的问题了。

    接下来我们通过一个范例来展示一下如何使用 Python 来实现上文范例中所实现的功能。

    首先,我们创建一个叫 app 的目录,在这个文件夹中添加一个包含以下内容的 README 文件。


    清单 5:README 文件内容

    				
    这是一个安装包的范例展示

    将 app 文件夹压缩成 app.tar.gz

    然后,编写 Python 的安装控制脚本。内容如下:


    清单 6:install.py

    				
     if __name__ == '__main__': 
        import os 
        import base64 
    
        print 'Spliting binary data to app.tar.gz ...'
        open('tmp.base64', 'w').write(get_data()) 
        base64.decode(open('tmp.base64', 'r'), 
                           open('app.tar.gz', 'w')) 
        os.remove('tmp.base64') 
        os.mkdir('temp') 
        os.system('tar -zxvf app.tar.gz -C temp')

    将这段代码保存为 install.py。

    接下来,我们需要把 app.tar.gz 文件转换成 Base64 编码。我们可以通过如下 Python 代码来实现这个转换


    清单 7:使用 Python 生成 Base64 数据

    				
     import base64 
        base.encode(open('app.tar.gz', 'r'), 
                    open('app.tar.gz.base64', 'w'))

    输出的 app.tar.gz.base64 文件中包含了描述这个压缩包的 Base64 编码。

    接下来有两个问题:

    • 我们不能直接把这个文件和 install.py 合并。因为单纯的字符串在 Python 脚本中没办法访问。所以我们需要作些加工。加工的方法可以是声明一个叫做“data”的变量,也可以定义一个叫“get_data”的方法。本范例我们采用后者。
    • 我们不能简单的将包含 get_data 方法的文件直接 cat 到 install.py 的结尾。因为对于 __main__ 来说,这个方法没有声明。所以我们需要把 get_data 方法添加到 __main__ 的前面。

    这两步过程我们可以通过下面这段 Python 代码来处理


    清单 7:合并 install.py 和 Base64 数据文件

    				
     # create data template 
     data_template = [] 
     data_template.append('#!/usr/bin/env python\n') 
     data_template.append('def get_data():\n') 
     data_template.append('\treturn \'') 
     for line in lines: 
        data_template.append('%s\\\n' % line.strip()) 
     data_template.append('\'\n') 
    
     # create app.bin 
     install_temp = open('install.py', 'r').readlines() 
    
     app_bin = open('app.bin', 'w') 
    
     for line in data_template: 
        app_bin.write(line) 
     app_bin.write('\n') 
    
     for line in install_temp: 
        app_bin.write(line) 
    
     app_bin.flush() 
     app_bin.close()

    这样最终我们需要的 app.bin 文件就做好了,运行的效果和之前使用 Shell 脚本的 app.bin 是一样的。

    对于 Windows 平台来说,这个范例只需要将 app 文件夹压缩成 app.zip,解压的命令“tar”替换成 winzip 之类的工具即可。实际应用中,可以使用 Python 提供的“zipfile”这个包提供的接口来完成。这样就不牵扯平台相关的命令了。这里不对 zipfile 包做描述。

     

    更多的事情

    代码保密

    通过上面方法制作的 app.bin 文件实际上是一个纯文本的文件。用户可以使用任何文本编辑器对它进行编辑。

    如果不希望用户编辑文件中的内容,可以使用 Python 提供的”py_compile”包来实现。

    修改后的代码如下:


    清单 8:编译安装文件

    				
     # create data template 
     data_template = [] 
     data_template.append('#!/usr/bin/env python\n') 
     data_template.append('def get_data():\n') 
     data_template.append('\treturn \'') 
    
     lines = open('app.tar.gz.base64', 'r') 
     for line in lines: 
        data_template.append('%s\\\n' % line.strip()) 
     data_template.append('\'\n') 
    
     # create app.bin 
     install_temp = open('install.py', 'r').readlines() 
    
     app_bin = open('app.py', 'w') 
    
     for line in data_template: 
        app_bin.write(line) 
        app_bin.write('\n') 
    
        for line in install_temp: 
            app_bin.write(line) 
    
        app_bin.flush() 
        app_bin.close() 
    
        py_compile.compile('app.py') 
        os.rename('app.pyc', 'app.bin')

    注册表访问

    对于 Windows 平台,安装程序可能需要访问 Windows 的注册表。Python 提供了一个 _winreg 的库可以完成这方面的操作。

    局限与可能的解决方案

    对于没有安装 Python 的系统,如同现在很多的 Bin 安装程序在使用前需要安装 Java 环境一样, 使用 Python 制作的安转程序之前需要先安装 Python。对于用户来说可能会感觉比较不适。对于 Windows 平台,这个问题可以通过类似 py2exe 的方式,将安装包依赖的 Python 库封装到安装包自己内部来解决。

    python,go,redis,mongodb,.net,C#,F#,服务器架构
  • 相关阅读:
    OKHttp使用详解
    spring okhttp3
    HTTPS SSL/TLS协议
    springboot @scheduled 并发
    spring @Scheduled 并发
    CORSFilter 跨域资源访问
    定时任务 spring @Scheduled注解
    spring proxy-target-class
    iOS 适用于Pad上的菜单弹出界面-最简单的一种实现记录
    Mac系统清理、占用空间大、空间不够、查看系统文件大小分布
  • 原文地址:https://www.cnblogs.com/descusr/p/2845522.html
Copyright © 2011-2022 走看看