summaryrefslogtreecommitdiffstats
blob: b49ef7f983c263f26807b5e40b1ee316f62441ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env python2
#
# This Python script builds self-contained Linux packages, i.e. AppImages ( http://appimage.org/ ), of Swift.
# It downloads required appimage tool that is executed at the end and an exculde list from the internet.
#
# Non-standard dependencies:
# * plumbum ( https://plumbum.readthedocs.io/en/latest/ )
# * click ( http://click.pocoo.org/5/ )
#
# They can be installed via `pip install plumbum click`.
#
# The script requires git, wget, readelf and objcopy to work and will fail if
# they are not present.
#
# Just run ./Swift/Packaging/appimage/build_appimage.py and after it executed
# there should be .appimage and .debug files in Packages/Swift.
#
# The appimage will be for the same architecture as the host architecture.
# Pass `--qt5=False` to the tool to build against legacy Qt4.
#
# To include newer libstdc++.so or similar libs in an AppImage, use -li parameters.

from plumbum import local, FG, BG, RETCODE, colors, commands
import click
import os
import re
import time
import sys

git = local['git']

def git_working_dir():
    working_dir = git('rev-parse', '--show-toplevel').strip()
    if not os.path.exists(working_dir):
        working_dir = os.path.dirname(os.path.abspath(git('rev-parse', '--git-dir').strip()))
    return working_dir

git_working_dir = git_working_dir()
resources_dir = os.path.join(git_working_dir, "Swift/resources/")

@click.command()
@click.option('--qt5', default=True, type=bool, help='Build with Qt5.')
@click.option('--includelib', '-il', type=click.Path(), multiple=True, help='Copy extra library into AppImage.')
def build_appimage(qt5, includelib):
    print(colors.bold & colors.info | "Switch to git working directory root " + git_working_dir)
    with local.cwd(git_working_dir):

        def copy_dependencies_into_appdir(excludelist, binary_path, lib_path):
            chain = local['ldd'][binary_path] | local['grep']["=> /"] | local['awk']['{print $3}']
            for dynamic_lib in chain().strip().split('\n'):
                if os.path.basename(dynamic_lib) in excludelist:
                    #print(colors.info | "Not including " + dynamic_lib + " as it is blacklisted.")
                    pass
                else:
                    local['cp']('-v', '-L', dynamic_lib, lib_path)

        scons = local['./scons']
        print(colors.bold & colors.info | "Building Swift")
        scons['qt5={0}'.format("1" if qt5 else "0"), 'Swift'] & FG

        swift = local['./Swift/QtUI/swift-im']
        swift_version = swift('--version').strip()
        print(colors.bold & colors.info | "Successfully built " + swift_version)

        swift_architecture_string = ""
        try:
            readelf_on_swift_im_output = local['readelf']('-h', './Swift/QtUI/swift-im').strip()
            swift_architecture = re.search(r'Class:\s+([^\s]+)', readelf_on_swift_im_output, flags=re.I).group(1)
            if swift_architecture == "ELF32":
                swift_architecture_string = ".i386"
            elif swift_architecture == "ELF64":
                swift_architecture_string = ".amd64"
        except:
            pass

        appdir_path = os.path.join(git_working_dir, 'Swift/Packaging/AppImage/Swift.AppDir')
        print(colors.bold & colors.info | "Prepare AppDir structure at " + appdir_path)
        local['mkdir']('-p', appdir_path)

        swift_install_dir = os.path.join(appdir_path, 'usr')
        print(colors.bold & colors.info | "Install Swift to AppDir")
        scons['qt5={0}'.format("1" if qt5 else "0"), 'Swift', 'SWIFT_INSTALLDIR=' + swift_install_dir , swift_install_dir] & FG

        print(colors.bold & colors.info | "Download dynamic lib exclude list from https://raw.githubusercontent.com/AppImage/AppImages/master/excludelist")
        local['wget']['--no-check-certificate', '-O', '/tmp/excludelist', 'https://raw.githubusercontent.com/AppImage/AppImages/master/excludelist'] & FG

        print(colors.bold & colors.info | "Load dynamic library exclude list")
        excludelist = set()
        with open('/tmp/excludelist') as f:
            for line in f:
                if line and "#" not in line:
                    excludelist.add(line.strip())

        print(colors.bold & colors.info | "Copy dynamic library dependencies into AppDir")
        local['mkdir']('-p', os.path.join(appdir_path, 'usr/lib'))
        appdir_swift_im_binary = os.path.join(swift_install_dir, 'bin/swift-im')
        copy_dependencies_into_appdir(excludelist, appdir_swift_im_binary, os.path.join(swift_install_dir, "lib"))

        if qt5:
            print(colors.bold & colors.info | "Analyze binary for Qt plugin paths")
            # Run Swift binary and parse debug output to find plugin paths to copy.
            swift_plugin_debug_output = ""
            try:
                process = local["env"]["QT_DEBUG_PLUGINS=1", './Swift/QtUI/swift-im'].popen()
                time.sleep(2)
                process.kill()
                stdout, stderr = process.communicate()
                swift_plugin_debug_output = stderr
            except:
                pass
            if swift_plugin_debug_output:
                matches = re.search(r"/.*/qt5/plugins", swift_plugin_debug_output)
                if matches:
                    qt5_plugin_path = matches.group(0)
                    appdir_qt5_plugin_path = os.path.join(appdir_path, 'usr/lib/qt5')
                    local['mkdir']('-p', appdir_qt5_plugin_path)
                    local['cp']('-r', '-L', qt5_plugin_path, appdir_qt5_plugin_path)

                    print(colors.bold & colors.info | "Copy plugin dependencies into AppDir")
                    for plugin_path in local.path(appdir_qt5_plugin_path) // "plugins/*/*.so":
                        copy_dependencies_into_appdir(excludelist, plugin_path, os.path.join(swift_install_dir, "lib"))

        if includelib:
            for includelib_path in includelib:
                print(colors.bold & colors.info | "Copy " + includelib_path + " to AppDir.")
                local['cp']('-v', '-L', includelib_path, os.path.join(swift_install_dir, "lib"))

        print(colors.bold & colors.info | "Download https://github.com/AppImage/AppImageKit/raw/appimagetool/master/resources/AppRun to " + os.path.join(appdir_path, 'AppRun'))
        local['wget']('--no-check-certificate', '-O', os.path.join(appdir_path, 'AppRun'), "https://github.com/AppImage/AppImageKit/raw/appimagetool/master/resources/AppRun")
        local['chmod']('+x', os.path.join(appdir_path, 'AppRun'))

        print(colors.bold & colors.info | "Copy swift.desktop file")
        source_desktop_file = os.path.join(resources_dir, "swift.desktop")
        target_desktop_file = os.path.join(appdir_path, 'swift.desktop')
        local['cp']('-v', '-L', source_desktop_file, target_desktop_file)

        print(colors.bold & colors.info | "Copy logos to Swift.AppDir")
        local['cp']('-v', '-L', os.path.join(resources_dir, "logo/logo-icon-512.png"), os.path.join(appdir_path, "swift.png"))
        local['cp']('-v', '-L', os.path.join(resources_dir, "logo/logo-icon.svg"), os.path.join(appdir_path, "swift.svg"))
        local['cp']('-v', '-L', os.path.join(resources_dir, "logo/logo-icon-32.xpm"), os.path.join(appdir_path, "swift.xpm"))

        print(colors.bold & colors.info | "Download appimagetool to /tmp/appimagetool and make it executable")
        appimage_url_suffix = ""
        if swift_architecture_string == ".amd64":
            appimage_url_suffix = "x86_64.AppImage"
        else:
            appimage_url_suffix = "i686.AppImage"
        local['wget']['-O', '/tmp/appimagetool', 'https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-' + appimage_url_suffix] & FG
        local['chmod']['a+x', '/tmp/appimagetool']()

        local['mkdir']('-p', os.path.join(git_working_dir, "Packages/Swift"))
        appimage_path = os.path.join(git_working_dir, "Packages/Swift/" + swift_version.replace(" ", "-") + swift_architecture_string + ".appimage")
        debug_symbol_path = os.path.join(git_working_dir, "Packages/Swift/" + swift_version.replace(" ", "-") + swift_architecture_string + ".debug")

        print(colors.bold & colors.info | "Extract debug information from swift-im")
        local['objcopy']('--only-keep-debug', appdir_swift_im_binary, debug_symbol_path)
        local['strip']('-g', appdir_swift_im_binary)
        debuglink_retcode = local['objcopy']['--add-gnu-debuglink', debug_symbol_path, os.path.join(swift_install_dir, 'bin/swift-im')] & RETCODE
        if debuglink_retcode != 0:
            print(colors.bold & colors.warn | "Failed to create debuglink in binary.")

        print(colors.bold & colors.info | "Generate AppImage from Swift.AppDir")
        local['/tmp/appimagetool'][appdir_path, appimage_path] & FG


if __name__ == '__main__':
    build_appimage()