| Differences between
and this patch
- a/Tools/ChangeLog +29 lines
Lines 1-3 a/Tools/ChangeLog_sec1
1
2020-08-07  Carlos Alberto Lopez Perez  <clopez@igalia.com>
2
3
        [GTK][WPE] Add a script for generating MiniBrowser bundles
4
        https://bugs.webkit.org/show_bug.cgi?id=215266
5
6
        Reviewed by NOBODY (OOPS!).
7
8
        This converts the previous generate-jsc-bundle into a new script
9
        that is now able to do the following:
10
         - generate a jsc bundle
11
         - generate a MiniBrowse bundle
12
         - generate an all bundle (jsc+MiniBrowser)
13
        The bundle can include all the system-libraries from the system,
14
        so that way (in theory) the bundle would run on any other distribution
15
        or it can generate an install-dependencies script so it generates
16
        a lightweight bundle with only the minimum libraries included that
17
        would run only on the distribution where it has been created
18
        (after running the install-dependencies script)
19
20
        We already have a bot generating the jsc bundle and we plan to also
21
        have bots for generating the MiniBrowser bundles as well.
22
23
        * BuildSlaveSupport/build.webkit.org-config/steps.py: Update the step for the new command.
24
        (GenerateJSCBundle):
25
        * Scripts/generate-bundle: Added.
26
        * Scripts/generate-jsc-bundle: Removed.
27
        * jhbuild/jhbuildutils.py:
28
        (enter_jhbuild_environment_if_available): Unicode argument not longer available on python3's gettext.install()
29
1
2020-08-07  Youenn Fablet  <youenn@apple.com>
30
2020-08-07  Youenn Fablet  <youenn@apple.com>
2
31
3
        Introduce a Vector::isolatedCopy() &&
32
        Introduce a Vector::isolatedCopy() &&
- a/Tools/BuildSlaveSupport/build.webkit.org-config/steps.py -3 / +4 lines
Lines 301-309 class ArchiveMinifiedBuiltProduct(ArchiveBuiltProduct): a/Tools/BuildSlaveSupport/build.webkit.org-config/steps.py_sec1
301
301
302
302
303
class GenerateJSCBundle(shell.ShellCommand):
303
class GenerateJSCBundle(shell.ShellCommand):
304
    command = ["python", "./Tools/Scripts/generate-jsc-bundle", "--builder-name", WithProperties("%(buildername)s"),
304
    command = ["./Tools/Scripts/generate-bundle", "--builder-name", WithProperties("%(buildername)s"),
305
               WithProperties("--platform=%(fullPlatform)s"), WithProperties("--%(configuration)s"),
305
               "--bundle=jsc", "--syslibs=bundle-all", WithProperties("--platform=%(fullPlatform)s"),
306
               WithProperties("--revision=%(got_revision)s"), "--remote-config-file", "../../remote-jsc-bundle-upload-config.json"]
306
               WithProperties("--%(configuration)s"), WithProperties("--revision=%(got_revision)s"),
307
               "--remote-config-file", "../../remote-jsc-bundle-upload-config.json"]
307
    name = "generate-jsc-bundle"
308
    name = "generate-jsc-bundle"
308
    description = ["generating jsc bundle"]
309
    description = ["generating jsc bundle"]
309
    descriptionDone = ["generated jsc bundle"]
310
    descriptionDone = ["generated jsc bundle"]
- a/Tools/Scripts/generate-bundle +642 lines
Line 0 a/Tools/Scripts/generate-bundle_sec1
1
#!/usr/bin/env python3
2
#
3
# Copyright (C) 2018, 2020 Igalia S.L.
4
#
5
# Redistribution and use in source and binary forms, with or without
6
# modification, are permitted provided that the following conditions are met:
7
#
8
# 1. Redistributions of source code must retain the above copyright notice, this
9
#    list of conditions and the following disclaimer.
10
# 2. Redistributions in binary form must reproduce the above copyright notice,
11
#    this list of conditions and the following disclaimer in the documentation
12
#    and/or other materials provided with the distribution.
13
#
14
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
25
import argparse
26
import base64
27
import datetime
28
import hashlib
29
import json
30
import logging
31
import os
32
import shutil
33
import subprocess
34
import sys
35
import tempfile
36
import zipfile
37
38
top_level_directory = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..'))
39
sys.path.insert(0, os.path.join(top_level_directory, 'Tools', 'flatpak'))
40
sys.path.insert(0, os.path.join(top_level_directory, 'Tools', 'jhbuild'))
41
import jhbuildutils
42
import flatpakutils
43
44
45
INSTALL_DEPS_SCRIPT_TEMPLATE = """\
46
#!/bin/bash
47
set -eu -o pipefail
48
49
REQUIREDPACKAGES="%(packages_needed)s"
50
51
if ! which apt-get >/dev/null; then
52
    echo "This script only supports apt-get based distributions like Debian or Ubuntu."
53
    exit 1
54
fi
55
56
# Calling dpkg-query is slow, so call it only once and cache the results
57
TMPCHECKPACKAGES="$(mktemp)"
58
dpkg-query --show --showformat='${binary:Package} ${db:Status-Status}\\n' > "${TMPCHECKPACKAGES}"
59
TOINSTALL=""
60
for PACKAGE in ${REQUIREDPACKAGES}; do
61
    if ! grep -qxF "${PACKAGE} installed" "${TMPCHECKPACKAGES}"; then
62
        TOINSTALL="${TOINSTALL} ${PACKAGE}"
63
    fi
64
done
65
rm -f "${TMPCHECKPACKAGES}"
66
67
if [[ -z "${TOINSTALL}" ]]; then
68
    echo "All required dependencies are already installed"
69
else
70
    AUTOINSTALL=""
71
    [[ ${#} -gt 0 ]] && [[ "${1}" == "--autoinstall" ]] && AUTOINSTALL="-y" && export DEBIAN_FRONTEND="noninteractive"
72
    SUDO=""
73
    [[ ${UID} -ne 0 ]] && SUDO="sudo --preserve-env=DEBIAN_FRONTEND"
74
75
    echo "Need to install the following extra packages: ${TOINSTALL}"
76
    set -x
77
    ${SUDO} apt-get install --no-install-recommends ${AUTOINSTALL} ${TOINSTALL}
78
fi
79
"""
80
81
_log = logging.getLogger(__name__)
82
LOG_MESSAGE = 25
83
84
class BundleCreator(object):
85
86
    def __init__(self, configuration, platform, bundle_type, syslibs, should_strip_objects, compression_type, destination = None, revision = None, builder_name = None):
87
        self._configuration = configuration
88
        self._platform = platform.lower()
89
        self._revision = revision
90
        self._bundle_binaries = ['jsc', 'MiniBrowser'] if bundle_type == 'all' else [ bundle_type ]
91
        self._bundle_type = bundle_type
92
        self._buildername = builder_name
93
        self._syslibs = syslibs
94
        self._should_strip_objects = should_strip_objects
95
        self._compression_type = compression_type
96
        self._tmpdir = None
97
        self._wrapper_scripts = []
98
        self._port_binary_preffix = 'WebKit' if self._platform == 'gtk' else 'WPE'
99
        wk_build_path = os.environ['WEBKIT_OUTPUTDIR'] if 'WEBKIT_OUTPUTDIR' in os.environ else \
100
                        os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'WebKitBuild'))
101
        self._buildpath = os.path.join(wk_build_path, self._configuration.capitalize())
102
103
        default_bundle_name = bundle_type  + '_' + self._platform + '_' + self._configuration + '.' + self._compression_type
104
        if destination and  os.path.isdir(destination):
105
            self._bundle_file_path = os.path.join(destination, default_bundle_name)
106
        else:
107
            self._bundle_file_path = os.path.join(wk_build_path, default_bundle_name)
108
109
110
    def _create_tempdir(self, basedir = None):
111
        if basedir is not None:
112
            if not os.path.isdir(basedir):
113
                raise ValueError('%s is not a directory' % basedir)
114
            return tempfile.mkdtemp(prefix=os.path.join(os.path.abspath(basedir), 'tmp'))
115
        return tempfile.mkdtemp()
116
117
    def _run_cmd_and_get_output(self, command):
118
        command_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8')
119
        stdout, stderr = command_process.communicate()
120
        return command_process.returncode, stdout, stderr
121
122
123
    def _ldd_get_libs_and_interpreter(self, object):
124
        interpreter = None
125
        retcode, stdout, stderr = self._run_cmd_and_get_output(['ldd', object])
126
        if retcode != 0:
127
            raise RuntimeError('The ldd command returned non-zero status for object %s' % object)
128
        libs = []
129
        for line in stdout.splitlines():
130
            line = line.strip()
131
            if '=>' in line:
132
                line = line.split('=>')[1].strip()
133
                if 'not found' in line:
134
                    raise RuntimeError('ldd can not resolve all dependencies for object %s.' % object)
135
                line = line.split(' ')[0].strip()
136
                if os.path.isfile(line):
137
                    libs.append(line)
138
            else:
139
                line = line.split(' ')[0].strip()
140
                if os.path.isfile(line):
141
                    interpreter = line
142
        return libs, interpreter
143
144
145
    def _ldd_recursive_get_libs_and_interpreter(self, object, already_checked_libs = []):
146
        libs, interpreter = self._ldd_get_libs_and_interpreter(object)
147
        if libs:
148
            for lib in libs:
149
                if lib in already_checked_libs:
150
                    continue
151
                # avoid recursion loops (libfreetype.so.6 <-> libharfbuzz.so.0)
152
                already_checked_libs.append(lib)
153
                sub_libs, sub_interpreter = self._ldd_recursive_get_libs_and_interpreter(lib, already_checked_libs)
154
                libs.extend(sub_libs)
155
                if sub_interpreter and interpreter and sub_interpreter != interpreter:
156
                    raise RuntimeError('library %s has interpreter %s but object %s has interpreter %s' % (lib, sub_interpreter, object, interpreter))
157
        return list(set(libs)), interpreter
158
159
160
    def _get_osprettyname(self):
161
        with open('/etc/os-release', 'r') as osrelease_handle:
162
            for line in osrelease_handle.readlines():
163
                if line.startswith('PRETTY_NAME='):
164
                    return line.split('=')[1].strip().strip('"')
165
        return None
166
167
168
    def _generate_readme(self):
169
        _log.info('Generate README.txt file')
170
        readme_file = os.path.join(self._tmpdir, 'README.txt')
171
        with open(readme_file, 'w') as readme_handle:
172
            readme_handle.write('Bundle details:\n')
173
            readme_handle.write('  - WebKit Platform: %s\n' % self._platform.upper())
174
            readme_handle.write('  - Configuration: %s\n' % self._configuration.capitalize())
175
            if self._revision:
176
                readme_handle.write('  - WebKit Revision: %s\n' % self._revision)
177
            readme_handle.write('  - Bundle type: %s\n' % self._bundle_type)
178
            if self._buildername:
179
                readme_handle.write('  - Builder name: %s\n' % self._buildername)
180
            readme_handle.write('  - Builder date: %s\n' % datetime.datetime.now().isoformat())
181
            readme_handle.write('  - Builder OS: %s\n' % self._get_osprettyname())
182
            if self._syslibs == 'generate-install-script':
183
                readme_handle.write('\nRequired dependencies:\n')
184
                readme_handle.write('  - This bundle depends on several system libraries that are assumed to be installed.\n')
185
                readme_handle.write('  - To ensure all the required libraries are installed execute the script: install-dependencies.sh\n')
186
                readme_handle.write('  - You can pass the flag "--autoinstall" to this script to automatically install the dependencies if needed.\n')
187
            readme_handle.write('\nRun instructions:\n')
188
            scripts = "script" if len(self._wrapper_scripts) == 1 else "scripts"
189
            readme_handle.write('  - Execute the wrapper %s in this directory:\n' % scripts)
190
            for wrapper_script in self._wrapper_scripts:
191
                readme_handle.write('    * %s\n' %wrapper_script)
192
        return True
193
194
195
    def _generate_wrapper_script(self, interpreter, binary_to_wrap):
196
        if not os.path.isfile(os.path.join(self._tmpdir, 'bin', binary_to_wrap)):
197
            raise RuntimeError('Can not find binary to wrap for %s' % binary_to_wrap)
198
        self._wrapper_scripts.append(binary_to_wrap)
199
        _log.info('Generate wrapper script %s' % binary_to_wrap)
200
        script_file = os.path.join(self._tmpdir, binary_to_wrap)
201
202
        with open(script_file, 'w') as script_handle:
203
            script_handle.write('#!/bin/sh\n')
204
            script_handle.write('MYDIR="$(dirname $(readlink -f $0))"\n')
205
            script_handle.write('export LD_LIBRARY_PATH="${MYDIR}/lib"\n')
206
            if os.path.isdir(os.path.join(self._tmpdir, 'gio')):
207
                gio_var = 'GIO_MODULE_DIR' if self._syslibs == 'bundle-all' else 'GIO_EXTRA_MODULES'
208
                script_handle.write('export %s="${MYDIR}/gio"\n' % gio_var)
209
            if os.path.isdir(os.path.join(self._tmpdir, 'gst')):
210
                gst_var = 'GST_PLUGIN_SYSTEM_PATH_1_0' if self._syslibs == 'bundle-all' else 'GST_PLUGIN_PATH_1_0'
211
                script_handle.write('export %s="${MYDIR}/gst"\n' % gst_var)
212
                script_handle.write('export GST_REGISTRY_1_0="${MYDIR}/gst/gstreamer-1.0.registry"\n')
213
            if binary_to_wrap != "jsc":
214
                script_handle.write('export WEBKIT_EXEC_PATH="${MYDIR}/bin"\n')
215
                script_handle.write('export WEBKIT_INJECTED_BUNDLE_PATH="${MYDIR}/lib"\n')
216
            if self._syslibs == 'bundle-all':
217
                script_handle.write('INTERPRETER="${MYDIR}/lib/%s"\n' % os.path.basename(interpreter))
218
                if binary_to_wrap != "jsc":
219
                    script_handle.write('export WEB_PROCESS_CMD_PREFIX="${INTERPRETER}"\n')
220
                    script_handle.write('export PLUGIN_PROCESS_CMD_PREFIX="${INTERPRETER}"\n')
221
                    script_handle.write('export NETWORK_PROCESS_CMD_PREFIX="${INTERPRETER}"\n')
222
                    script_handle.write('export GPU_PROCESS_CMD_PREFIX="${INTERPRETER}"\n')
223
                script_handle.write('exec "${INTERPRETER}" "${MYDIR}/bin/%s" "$@"\n' % binary_to_wrap)
224
            else:
225
                script_handle.write('exec "${MYDIR}/bin/%s" "$@"\n' % binary_to_wrap)
226
        os.chmod(script_file, 0o755)
227
228
    def _generate_install_deps_script(self, system_packages_needed):
229
        if not system_packages_needed:
230
            return
231
        if 'MiniBrowser' in self._bundle_binaries:
232
            # Add some extra packages that are needed but the script can't automatically detect
233
            for extra_needed_pkg in ['ca-certificates', 'shared-mime-info']:
234
                system_packages_needed.add(extra_needed_pkg)
235
            # And remove some packages that may be detected due to indirect deps (gstreamer/gio) but are really not needed
236
            for not_needed_pkg in ['dconf-gsettings-backend', 'gvfs', 'pitivi', 'gstreamer1.0-convolver-pulseeffects', 'gstreamer1.0-x',
237
                                   'gstreamer1.0-adapter-pulseeffects', 'gstreamer1.0-autogain-pulseeffects', 'gstreamer1.0-alsa',
238
                                   'gstreamer1.0-clutter-3.0', 'gstreamer1.0-crystalizer-pulseeffects', 'gstreamer1.0-gtk3', 'gstreamer1.0-nice']:
239
                if not_needed_pkg in system_packages_needed:
240
                    system_packages_needed.remove(not_needed_pkg)
241
                # Sometimes the package is identified with an arch suffix, but not always
242
                not_needed_pkg_arch = not_needed_pkg + ':amd64'
243
                if not_needed_pkg_arch in system_packages_needed:
244
                    system_packages_needed.remove(not_needed_pkg_arch)
245
        installdeps_file = os.path.join(self._tmpdir, 'install-dependencies.sh')
246
        with open(installdeps_file, 'w') as installdeps_handle:
247
            installdeps_handle.write(INSTALL_DEPS_SCRIPT_TEMPLATE % {'packages_needed' : ' '.join(system_packages_needed)} )
248
        os.chmod(installdeps_file, 0o755)
249
250
251
    def _copy_and_remove_rpath(self, orig_file, type='bin', destination_dir=None):
252
        if not destination_dir:
253
            dir_suffix = 'lib' if type == 'interpreter' else type
254
            destination_dir = os.path.join(self._tmpdir, dir_suffix)
255
        if not os.path.isdir(destination_dir):
256
            os.makedirs(destination_dir)
257
258
        if not os.path.isfile(orig_file):
259
            raise ValueError('Can not find file %s' % orig_file)
260
261
        _log.info('Add to bundle [%s]: %s' % (type, orig_file))
262
        shutil.copy(orig_file, destination_dir)
263
264
        if shutil.which('patchelf'):
265
            patch_elf_command = ['patchelf', '--remove-rpath', os.path.join(destination_dir, os.path.basename(orig_file))]
266
            if subprocess.call(patch_elf_command) != 0:
267
                _log.warning('The patchelf command returned non-zero status')
268
        else:
269
                _log.warning('patchelf not found. Not modifying rpath')
270
271
        if shutil.which('strip'):
272
            strip_command = ['strip', '--strip-unneeded', os.path.join(destination_dir, os.path.basename(orig_file))]
273
            if subprocess.call(strip_command) != 0:
274
                _log.warning('The strip command returned non-zero status')
275
        else:
276
            _log.warning('strip not found. Not stripping object')
277
278
    def _remove_tempdir(self):
279
        if not self._tmpdir:
280
            return
281
        if os.path.isdir(self._tmpdir):
282
            shutil.rmtree(self._tmpdir)
283
284
    def create(self):
285
        self._tmpdir = self._create_tempdir(self._buildpath)
286
287
        if os.path.isfile(self._bundle_file_path):
288
            _log.info('Removing previous bundle %s' % self._bundle_file_path)
289
            os.remove(self._bundle_file_path)
290
291
        for bundle_binary in self._bundle_binaries:
292
            self._create_bundle(bundle_binary)
293
        self._generate_readme()
294
        if self._compression_type == 'zip':
295
            self._create_zip()
296
        else:
297
            raise NotImplementedError('Support for compression type %s not implemented' % self._compression_type)
298
        self._remove_tempdir()
299
        if not os.path.isfile(self._bundle_file_path):
300
            raise RuntimeError('Unable to create the file %s' % self._bundle_file_path)
301
        _log.log(LOG_MESSAGE, 'Bundle file created at: %s' % self._bundle_file_path)
302
        return self._bundle_file_path
303
304
305
    def _get_webkit_binaries(self):
306
        webkit_binaries = []
307
        bin_dir = os.path.join(self._buildpath, 'bin')
308
        for entry in os.listdir(bin_dir):
309
            if entry.startswith(self._port_binary_preffix) and (entry.endswith('Process') or entry.endswith('Driver')):
310
                binary = os.path.join(bin_dir, entry)
311
                if os.path.isfile(binary) and os.access(binary, os.X_OK):
312
                    webkit_binaries.append(binary)
313
        if len(webkit_binaries) < 2:
314
            raise RuntimeError('Could not find required WebKit Process binaries. Check if you are passing the right platform value.')
315
        return webkit_binaries
316
317
318
    def _get_webkit_bundlelib(self):
319
        lib_dir = os.path.join(self._buildpath, 'lib')
320
        bundle_lib = None
321
        for entry in os.listdir(lib_dir):
322
            if entry.endswith('.so') and 'injectedbundle' in entry.lower() and 'test' not in entry.lower():
323
                assert(bundle_lib == None)
324
                bundle_lib = os.path.join(lib_dir, entry)
325
                break
326
        assert(bundle_lib)
327
        return bundle_lib
328
329
330
    def _create_zip(self):
331
        _log.info('Create ZIP file')
332
        with zipfile.ZipFile(self._bundle_file_path, 'w', compression=zipfile.ZIP_DEFLATED) as zipHandle:
333
            for dirname, subdirs, files in os.walk(self._tmpdir):
334
                for filename in files:
335
                    system_file_path = os.path.join(dirname, filename)
336
                    zip_file_path = system_file_path.replace(self._tmpdir, '', 1).lstrip('/')
337
                    if os.path.islink(system_file_path):
338
                        symlink_zip_info = zipfile.ZipInfo(zip_file_path)
339
                        symlink_zip_info.create_system = 3 # Unix (for symlink support)
340
                        symlink_zip_info.external_attr = 0xA1ED0000 # Zip softlink magic number
341
                        zipHandle.writestr(symlink_zip_info, os.readlink(system_file_path))
342
                    else:
343
                        zipHandle.write(system_file_path, zip_file_path)
344
345
346
    def _get_system_package_name(self, object):
347
        if not shutil.which('dpkg'):
348
            raise RuntimeError('Adding system dependencies only supported for dpkg-based distros. Try passing --syslibs=bundle-all')
349
        retcode, stdout, stderr = self._run_cmd_and_get_output(['dpkg', '-S', object])
350
        if retcode != 0:
351
            # Give a second-try with the realpath of the object.
352
            # This fixes issue on Ubuntu-20.04 that has a /lib symlink to /usr/lib
353
            # and objects point to /lib, but dpkg only recognizes the files on /usr/lib
354
            object_realpath = os.path.realpath(object)
355
            if object_realpath != object:
356
                retcode, stdout, stderr = self._run_cmd_and_get_output(['dpkg', '-S', object_realpath])
357
        if retcode != 0:
358
            # Package not found
359
            return None
360
        package = stdout.split(' ')[0].rstrip(':')
361
        _log.info('Add dependency on system package [%s]: %s' %(package, object))
362
        return package
363
364
365
    def _get_gio_modules(self):
366
        gio_modules = []
367
        retcode, stdout, stderr = self._run_cmd_and_get_output(['pkg-config', '--variable=giomoduledir', 'gio-2.0'])
368
        if retcode != 0:
369
            raise RuntimeError('The pkg-config command returned status %d' % retcode)
370
        gio_module_dir = stdout.strip()
371
        if not os.path.isdir(gio_module_dir):
372
            raise RuntimeError('The pkg-config entry for giomoduledir is not a directory: %s' % gio_module_dir)
373
        for entry in os.listdir(gio_module_dir):
374
            if entry.endswith('.so'):
375
                gio_modules.append(os.path.join(gio_module_dir, entry))
376
        return gio_modules
377
378
    def _get_gstreamer_modules(self):
379
        gstreamer_plugins = []
380
        retcode, stdout, stderr = self._run_cmd_and_get_output(['pkg-config', '--variable=pluginsdir', 'gstreamer-1.0'])
381
        if retcode != 0:
382
            raise RuntimeError('The pkg-config command returned status %d' % retcode)
383
        gstramer_plugins_dir = stdout.strip()
384
        if not os.path.isdir(gstramer_plugins_dir):
385
            raise RuntimeError('The pkg-config entry for pluginsdir is not a directory: %s' % gstramer_plugins_dir)
386
        for entry in os.listdir(gstramer_plugins_dir):
387
            if entry.endswith('.so'):
388
                gstreamer_plugins.append(os.path.join(gstramer_plugins_dir, entry))
389
        return gstreamer_plugins
390
391
392
    def _add_object_or_get_sysdep(self, object, object_type):
393
        provided_by_system_package = None
394
        if self._syslibs == 'bundle-all':
395
            self._copy_and_remove_rpath(object, type=object_type)
396
        else:
397
            provided_by_system_package = self._get_system_package_name(object)
398
            if not provided_by_system_package:
399
                self._copy_and_remove_rpath(object, type=object_type)
400
        return provided_by_system_package
401
402
    def _ensure_wpe_backend_symlink(self):
403
        # WPE/WPERenderer dlopens this library without a version suffix,
404
        # so we need to ensure there is a proper symlink
405
        bundle_lib_dir = os.path.join(self._tmpdir, 'lib')
406
        wpe_backend_soname = 'libWPEBackend-fdo-1.0.so'
407
        previous_dir = os.getcwd()
408
        for entry in os.listdir(bundle_lib_dir):
409
            if entry.startswith(wpe_backend_soname + '.'):
410
                os.chdir(bundle_lib_dir)
411
                if not os.path.exists(wpe_backend_soname):
412
                    os.symlink(entry, wpe_backend_soname)
413
                os.chdir(previous_dir)
414
                break
415
416
    def _create_bundle(self, bundle_binary):
417
        main_binary_path = os.path.join(self._buildpath, 'bin', bundle_binary)
418
        if not os.path.isfile(main_binary_path) or not os.access(main_binary_path, os.X_OK):
419
            raise ValueError('Cannot find binary for %s at %s' % (bundle_binary, main_binary_path) )
420
421
        copied_interpreter = None
422
        gio_modules = []
423
        gstreamer_modules = []
424
        libraries_checked = set()
425
        system_packages_needed = set()
426
        objects_to_copy = [ main_binary_path ]
427
        if bundle_binary == 'MiniBrowser':
428
            gio_modules = self._get_gio_modules()
429
            gstreamer_modules = self._get_gstreamer_modules()
430
            objects_to_copy.extend(self._get_webkit_binaries())
431
            objects_to_copy.append(self._get_webkit_bundlelib())
432
            objects_to_copy.extend(gio_modules)
433
            objects_to_copy.extend(gstreamer_modules)
434
        for object in objects_to_copy:
435
            system_package = None
436
            if object in gio_modules:
437
                system_package = self._add_object_or_get_sysdep(object, 'gio')
438
                if system_package:
439
                    system_packages_needed.add(system_package)
440
            elif object in gstreamer_modules:
441
                system_package = self._add_object_or_get_sysdep(object, 'gst')
442
                if system_package:
443
                    system_packages_needed.add(system_package)
444
            elif object.endswith('.so'):
445
                self._copy_and_remove_rpath(object, type='lib')
446
            else:
447
                self._copy_and_remove_rpath(object, type='bin')
448
            # There is no need to examine the libraries linked with objects coming from a system package,
449
            # because system packages already declare dependencies between them.
450
            # However, if we are running with self._syslibs == 'bundle-all' then system_package will be None,
451
            # and everything will be examined and bundled as we don't account for system packages in that case.
452
            if not system_package:
453
                libraries, interpreter = self._ldd_recursive_get_libs_and_interpreter(object)
454
                if copied_interpreter is None:
455
                    if self._syslibs == 'bundle-all':
456
                        self._copy_and_remove_rpath(interpreter, type='interpreter')
457
                    copied_interpreter = interpreter
458
                elif copied_interpreter != interpreter:
459
                    raise RuntimeError('Detected binaries with different interpreters: %s != %s' %(copied_interpreter, interpreter))
460
                # FIXME: for --syslibs=bundle-all we would have to copy the libnss_*so* libraries which are dlopen'ed <https://bugs.debian.org/203014>
461
                # Also we should include config files like fontconfig stuff or support files like icons.
462
                for library in libraries:
463
                    if library in libraries_checked:
464
                        _log.debug('Skip already checked [lib]: %s' % library)
465
                        continue
466
                    libraries_checked.add(library)
467
                    system_package = self._add_object_or_get_sysdep(library, 'lib')
468
                    if system_package:
469
                        system_packages_needed.add(system_package)
470
471
        self._ensure_wpe_backend_symlink()
472
        self._generate_wrapper_script(interpreter, bundle_binary)
473
        if bundle_binary == "MiniBrowser":
474
            self._generate_wrapper_script(interpreter, self._port_binary_preffix + 'WebDriver')
475
        self._generate_install_deps_script(system_packages_needed)
476
477
478
class BundleUploader(object):
479
480
    def __init__(self, bundle_file_path, remote_config_file, bundle_type, platform, configuration, compression_type, revision, log_level):
481
        self._bundle_file_path = bundle_file_path
482
        self._remote_config_file = remote_config_file
483
        self._configuration = configuration
484
        self._revision = revision
485
        self._bundle_type = bundle_type
486
        self._platform = platform
487
        self._compression_type = compression_type
488
        self._sftp_quiet = log_level == 'quiet' or log_level == 'minimal'
489
        if not os.path.isfile(self._remote_config_file):
490
            raise ValueError('Can not find remote config file for upload at path %s' % self._remote_config_file)
491
492
    def _sha256sum(self, file):
493
        hash = hashlib.sha256()
494
        with open(file, 'rb') as f:
495
            for chunk in iter(lambda: f.read(4096), b''):
496
                hash.update(chunk)
497
        return hash.hexdigest()
498
499
    def _get_osidversion(self):
500
        with open('/etc/os-release', 'r') as osrelease_handle:
501
            for line in osrelease_handle.readlines():
502
                if line.startswith('ID='):
503
                    os_id = line.split('=')[1].strip().strip('"')
504
                if line.startswith('VERSION_ID='):
505
                    version_id = line.split('=')[1].strip().strip('"')
506
        assert(os_id)
507
        assert(version_id)
508
        osidversion = os_id + '-' + version_id
509
        assert(' ' not in osidversion)
510
        assert(len(osidversion) > 3)
511
        return osidversion
512
513
    # The expected format for --remote-config-file is something like:
514
    # {
515
    # "servername": "webkitgtk.org",
516
    # "serveraddress": "webkitgtk.intranet-address.local",
517
    # "serverport": "23",
518
    # "username": "upload-bot-64",
519
    # "baseurl": "https://webkitgtk.org/built-products",
520
    # "remotepath" : "x86_64/nightly/%(bundletype)s/%(distro_id_ver)s/%(bundletype)s_%(platform)s_%(configuration)s_r%(version)s.%(compression_type)s",
521
    # "sshkey": "output of the priv key in base64. E.g. cat ~/.ssh/id_rsa|base64 -w0"
522
    # }
523
    def upload(self):
524
        remote_data = json.load(open(self._remote_config_file))
525
        remote_file_bundle_path = remote_data['remotepath'] % { 'bundletype' : self._bundle_type,
526
                                                                'configuration' : self._configuration,
527
                                                                'compression_type' : self._compression_type,
528
                                                                'distro_id_ver' : self._get_osidversion().capitalize(),
529
                                                                'platform' : self._platform,
530
                                                                'version' : self._revision }
531
        with tempfile.NamedTemporaryFile(mode='w+b') as sshkeyfile, tempfile.NamedTemporaryFile(mode='w+') as hashcheckfile, \
532
             tempfile.NamedTemporaryFile(mode='w+') as lastisfile, tempfile.NamedTemporaryFile(mode='w+') as uploadinstructionsfile:
533
534
            # In theory NamedTemporaryFile() is already created 0600. But it don't hurts ensuring this again here.
535
            os.chmod(sshkeyfile.name, 0o600)
536
            sshkeyfile.write(base64.b64decode(remote_data['sshkey']))
537
            sshkeyfile.flush()
538
            # Generate and upload also a sha256 hash
539
            hashforbundle = self._sha256sum(self._bundle_file_path)
540
            os.chmod(hashcheckfile.name, 0o644)
541
            hashcheckfile.write('%s %s\n' % (hashforbundle, os.path.basename(remote_file_bundle_path)))
542
            hashcheckfile.flush()
543
            # A LAST-IS file for convenience
544
            os.chmod(lastisfile.name, 0o644)
545
            lastisfile.write('%s\n' % os.path.basename(remote_file_bundle_path))
546
            lastisfile.flush()
547
            # SFTP upload instructions file
548
            uploadinstructionsfile.write('progress\n')
549
            uploadinstructionsfile.write('put %s %s\n' % (self._bundle_file_path, remote_file_bundle_path))
550
            uploadinstructionsfile.write('put %s %s\n' % (hashcheckfile.name, remote_file_bundle_path + '.sha256sum'))
551
            uploadinstructionsfile.write('put %s %s\n' % (lastisfile.name, os.path.join(os.path.dirname(remote_file_bundle_path), 'LAST-IS')))
552
            uploadinstructionsfile.write('quit\n')
553
            uploadinstructionsfile.flush()
554
            # The idea of this is to ensure scp doesn't ask any question (not even on the first run).
555
            # This should be secure enough according to https://www.gremwell.com/ssh-mitm-public-key-authentication
556
            sftpCommand = ['sftp',
557
                           '-o', 'StrictHostKeyChecking=no',
558
                           '-o', 'UserKnownHostsFile=/dev/null',
559
                           '-o', 'LogLevel=ERROR',
560
                           '-P', remote_data['serverport'],
561
                           '-i', sshkeyfile.name,
562
                           '-b', uploadinstructionsfile.name,
563
                           '%s@%s' % (remote_data['username'], remote_data['serveraddress'])]
564
            _log.info('Uploading bundle to %s as %s with sha256 hash %s' % (remote_data['servername'], remote_file_bundle_path, hashforbundle))
565
            sftp_out = subprocess.DEVNULL if self._sftp_quiet else sys.stdout
566
            if subprocess.call(sftpCommand, stdout=sftp_out, stderr=sftp_out) != 0:
567
                raise RuntimeError('The sftp command returned non-zero status')
568
569
        _log.log(LOG_MESSAGE, 'Done: archive sucesfully uploaded to %s/%s' % (remote_data['baseurl'], remote_file_bundle_path))
570
        return 0
571
572
573
def configure_logging(selected_log_level='info'):
574
575
    class LogHandler(logging.StreamHandler):
576
        def __init__(self, stream):
577
             super().__init__(stream)
578
579
        def format(self, record):
580
            if record.levelno > LOG_MESSAGE:
581
                return '%s: %s' % (record.levelname, record.getMessage())
582
            return record.getMessage()
583
584
    logging.addLevelName(LOG_MESSAGE, 'MESSAGE')
585
    if selected_log_level == 'debug':
586
        log_level = logging.DEBUG
587
    elif selected_log_level == 'info':
588
        log_level = logging.INFO
589
    elif selected_log_level == 'quiet':
590
        log_level = logging.NOTSET
591
    elif selected_log_level == 'minimal':
592
        log_level = logging.getLevelName(LOG_MESSAGE)
593
594
    handler = LogHandler(sys.stdout)
595
    logger = logging.getLogger(__name__)
596
    logger.addHandler(handler)
597
    logger.setLevel(log_level)
598
    return handler
599
600
601
def main():
602
    parser = argparse.ArgumentParser('usage: %prog [options]')
603
    configuration = parser.add_mutually_exclusive_group(required=True)
604
    configuration.add_argument('--debug', action='store_const', const='debug', dest='configuration')
605
    configuration.add_argument('--release', action='store_const', const='release', dest='configuration')
606
    parser.add_argument('--platform', dest='platform', choices=['gtk', 'wpe'], required=True,
607
                        help='The WebKit port to generate the bundle')
608
    parser.add_argument('--bundle', dest='bundle_binary', choices=['jsc', 'MiniBrowser', 'all'], required=True,
609
                        help='Select what main binary should be included in the bundle')
610
    parser.add_argument('--syslibs', dest='syslibs', choices=['bundle-all', 'generate-install-script'], default='generate-install-script',
611
                        help='If value is "bundle-all", the bundle will include _all_ the system libraries instead of a install-dependencies script.\n'
612
                        'If value is "generate-install-script", the system libraries will not be bundled and a install-dependencies script will be generated for this distribution.')
613
    parser.add_argument('--compression', dest='compression', choices=['zip', 'tar.xz'], default='zip')
614
    parser.add_argument('--destination', action='store', dest='destination',
615
                        help='Optional path were to store the bundle')
616
    parser.add_argument('--no-strip', action='store_true', dest='no_strip',
617
                        help='Do not strip the binaries and libraries inside the bundle')
618
    parser.add_argument('--log-level', dest='log_level', choices=['quiet', 'minimal', 'info', 'debug'], default='info')
619
    parser.add_argument('--revision', action='store', dest='webkit_version')
620
    parser.add_argument('--builder-name', action='store', dest='builder_name')
621
    parser.add_argument('--remote-config-file', action='store', dest='remote_config_file',
622
                        help='Optional configuration file with the configuration needed to upload the generated the bundle to a remote server via sftp/ssh.')
623
    options = parser.parse_args()
624
625
    flatpakutils.run_in_sandbox_if_available([sys.argv[0], '--flatpak-' + options.platform] + sys.argv[1:])
626
    if not flatpakutils.is_sandboxed():
627
        jhbuildutils.enter_jhbuild_environment_if_available(options.platform)
628
629
    configure_logging(options.log_level)
630
    bundle_creator = BundleCreator(options.configuration, options.platform, options.bundle_binary, options.syslibs,
631
                                   not options.no_strip, options.compression, options.destination, options.webkit_version, options.builder_name)
632
    bundle_file_path = bundle_creator.create()
633
634
    if options.remote_config_file is not None:
635
        bundle_uploader = BundleUploader(bundle_file_path, options.remote_config_file, options.bundle_binary, options.platform,
636
                                         options.configuration, options.compression, options.webkit_version, options.log_level)
637
        return bundle_uploader.upload()
638
    return 0
639
640
641
if __name__ == '__main__':
642
    sys.exit(main())
- a/Tools/Scripts/generate-jsc-bundle -250 lines
Lines 1-250 a/Tools/Scripts/generate-jsc-bundle_sec1
1
#!/usr/bin/env python
2
#
3
# Copyright (C) 2018 Igalia S.L.
4
#
5
# Redistribution and use in source and binary forms, with or without
6
# modification, are permitted provided that the following conditions are met:
7
#
8
# 1. Redistributions of source code must retain the above copyright notice, this
9
#    list of conditions and the following disclaimer.
10
# 2. Redistributions in binary form must reproduce the above copyright notice,
11
#    this list of conditions and the following disclaimer in the documentation
12
#    and/or other materials provided with the distribution.
13
#
14
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
25
import base64
26
import datetime
27
import hashlib
28
import json
29
import optparse
30
import os
31
import shutil
32
import subprocess
33
import sys
34
import tempfile
35
import zipfile
36
37
top_level_directory = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', '..'))
38
sys.path.insert(0, os.path.join(top_level_directory, 'Tools', 'flatpak'))
39
sys.path.insert(0, os.path.join(top_level_directory, 'Tools', 'jhbuild'))
40
import jhbuildutils
41
import flatpakutils
42
43
44
# Ideally we should use something like lddtree or create our own version of that
45
# But in practice for jsc bundles there isn't recursive library entries, so we
46
# use standard ldd here just to avoid having to require lddtree.
47
def ldd_get_libs_and_interpreter(binary):
48
    lddCommand = ['ldd', binary]
49
    lddProcess = subprocess.Popen(lddCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
50
    stdout, stderr = lddProcess.communicate()
51
    if lddProcess.returncode != 0:
52
        raise RuntimeError('The ldd command returned non-zero status')
53
    libs = []
54
    for line in stdout.splitlines():
55
        line = line.strip()
56
        if '=>' in line:
57
            line = line.split('=>')[1].strip()
58
            if 'not found' in line:
59
                raise RuntimeError('Some dependencies can not be found with ldd.')
60
            line = line.split(' ')[0].strip()
61
            if os.path.isfile(line):
62
                libs.append(line)
63
        else:
64
            line = line.split(' ')[0].strip()
65
            if os.path.isfile(line):
66
                interpreter = line
67
    return libs, interpreter
68
69
70
def generate_readme(bundleTmpDir, builderName, configuration, platform, revision):
71
    print('Generate README.txt file')
72
    readmeFile = os.path.join(bundleTmpDir, 'README.txt')
73
    with open(readmeFile, 'w') as readmeHandle:
74
        readmeHandle.write('JSC bundle details\n')
75
        readmeHandle.write(' Builder name: %s\n' % builderName)
76
        readmeHandle.write(' Builder date: %s\n' % datetime.datetime.now().isoformat())
77
        readmeHandle.write(' Configuration: %s\n' % configuration)
78
        readmeHandle.write(' WebKit Platform: %s\n' % platform)
79
        readmeHandle.write(' WebKit Revision: %s\n' % revision)
80
        readmeHandle.write('\nInstructions: Execute the run-jsc wrapper script.\n')
81
    return True
82
83
84
def generate_wrapper_script(bundleTmpDir, interpreter):
85
    print('Generate wrapper script run-jsc')
86
    scriptFile = os.path.join(bundleTmpDir, 'run-jsc')
87
    with open(scriptFile, 'w') as scriptHandle:
88
        scriptHandle.write('#!/bin/sh\n')
89
        scriptHandle.write('MYDIR="$(dirname $(readlink -f $0))"\n')
90
        scriptHandle.write('export LD_LIBRARY_PATH="${MYDIR}/lib"\n')
91
        scriptHandle.write('exec "${MYDIR}/lib/%s" "${MYDIR}/bin/jsc" "$@"\n' % os.path.basename(interpreter))
92
    os.chmod(scriptFile, 0755)
93
94
95
def copy_and_remove_rpath(origFile, destinationDir, type='bin'):
96
    if not os.path.isfile(origFile):
97
        raise ValueError('Can not find file %s' % origFile)
98
    print('Copy to bundle [%s]: %s' % (type, origFile))
99
    shutil.copy(origFile, destinationDir)
100
    try:
101
        patchElfCommand = ['patchelf', '--remove-rpath', os.path.join(destinationDir, os.path.basename(origFile))]
102
        if subprocess.call(patchElfCommand) != 0:
103
            print('WARNING: The patchelf command returned non-zero status')
104
    except OSError as e:
105
        if e.errno == os.errno.ENOENT:
106
            print('WARNING: patchelf not found. Not modifying rpath')
107
        else:
108
            raise
109
110
111
def createJSCBundle(configuration, revision=None, builderName=None, platform=None):
112
    buildDir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'WebKitBuild'))
113
    binDir = os.path.join(buildDir, configuration.capitalize(), 'bin')
114
    libDir = os.path.join(buildDir, configuration.capitalize(), 'lib')
115
    jscBinary = os.path.join(binDir, 'jsc')
116
    if not os.path.isfile(jscBinary) or not os.access(jscBinary, os.X_OK):
117
        raise ValueError('Cannot find jsc at %s' % jscBinary)
118
119
    # Define names and paths for the generation of the bundle.
120
    bundleTmpDir = os.path.join(buildDir, 'jsc_tmp')
121
    bundleTmpLibDir = os.path.join(bundleTmpDir, 'lib')
122
    bundleTmpBinDir = os.path.join(bundleTmpDir, 'bin')
123
    bundleFileName = 'jsc_' + configuration
124
    bundleFileCompressed = os.path.join(buildDir, bundleFileName + '.zip')
125
126
    # Clean everything from previous runs
127
    if os.path.isdir(bundleTmpDir):
128
        shutil.rmtree(bundleTmpDir)
129
    if os.path.isfile(bundleFileCompressed):
130
        os.remove(bundleFileCompressed)
131
132
    # Create bundleTmpDir and put there everything needed.
133
    os.makedirs(bundleTmpDir)
134
    os.makedirs(bundleTmpLibDir)
135
    os.makedirs(bundleTmpBinDir)
136
    copy_and_remove_rpath(jscBinary, bundleTmpBinDir, type='bin')
137
    libraries, interpreter = ldd_get_libs_and_interpreter(jscBinary)
138
    copy_and_remove_rpath(interpreter, bundleTmpLibDir, type='interpreter')
139
    for library in libraries:
140
        copy_and_remove_rpath(library, bundleTmpLibDir, type='lib')
141
    generate_readme(bundleTmpDir, builderName, configuration, platform, revision)
142
    generate_wrapper_script(bundleTmpDir, interpreter)
143
144
    # jsvu project prefers .zip rather than .tar.xz
145
    with zipfile.ZipFile(bundleFileCompressed, 'w', compression=zipfile.ZIP_DEFLATED) as zipHandle:
146
        for dirname, subdirs, files in os.walk(bundleTmpDir):
147
            for filename in files:
148
                systemFilePath = os.path.join(dirname, filename)
149
                zipFilePath = systemFilePath.replace(bundleTmpDir, '', 1).lstrip('/')
150
                zipHandle.write(systemFilePath, zipFilePath)
151
152
    if not os.path.isfile(bundleFileCompressed):
153
        raise RuntimeError('Unable to create the file %s' % bundleFileCompressed)
154
    return bundleFileCompressed
155
156
157
def sha256sum(bundleFilePath):
158
    hash = hashlib.sha256()
159
    with open(bundleFilePath, 'rb') as f:
160
        for chunk in iter(lambda: f.read(4096), b''):
161
            hash.update(chunk)
162
    return hash.hexdigest()
163
164
165
# The expected format for --remote-config-file is something like:
166
# {
167
# "servername": "webkitgtk.org",
168
# "serveraddress": "webkitgtk.intranet-address.local",
169
# "serverport": "23",
170
# "username": "upload-bot-64",
171
# "baseurl": "https://webkitgtk.org/jsc-built-products/x86_64",
172
# "sshkey": "output of the priv key in base64. E.g. cat ~/.ssh/id_rsa|base64 -w0"
173
# }
174
def uploadJSCBundle(bundleFilePath, remoteConfigFile, configuration, revision):
175
    remoteData = json.load(open(remoteConfigFile))
176
    remoteFileName = str(revision) + '.zip'
177
    remoteFileBundlePathName = os.path.join(configuration, remoteFileName)
178
    remoteFileHashPathName = os.path.join(configuration, str(revision) + '.sha256sum')
179
    with tempfile.NamedTemporaryFile() as sshkeyfile:
180
        # In theory NamedTemporaryFile() is already created 0600. But it don't hurts ensuring this again here.
181
        os.chmod(sshkeyfile.name, 0600)
182
        sshkeyfile.write(base64.b64decode(remoteData['sshkey']))
183
        sshkeyfile.flush()
184
        # Generate and upload also a sha256 hash
185
        with tempfile.NamedTemporaryFile() as hashcheckfile:
186
            hashforbundle = sha256sum(bundleFilePath)
187
            os.chmod(hashcheckfile.name, 0644)
188
            hashcheckfile.write('%s %s\n' % (hashforbundle, remoteFileName))
189
            hashcheckfile.flush()
190
            with tempfile.NamedTemporaryFile() as uploadinstructionsfile:
191
                uploadinstructionsfile.write('progress\n')
192
                uploadinstructionsfile.write('put %s %s\n' % (bundleFilePath, remoteFileBundlePathName))
193
                uploadinstructionsfile.write('put %s %s\n' % (hashcheckfile.name, remoteFileHashPathName))
194
                uploadinstructionsfile.write('quit\n')
195
                uploadinstructionsfile.flush()
196
                # The idea of this is to ensure scp doesn't ask any question (not even on the first run).
197
                # This should be secure enough according to https://www.gremwell.com/ssh-mitm-public-key-authentication
198
                sftpCommand = ['sftp',
199
                               '-o', 'StrictHostKeyChecking=no',
200
                               '-o', 'UserKnownHostsFile=/dev/null',
201
                               '-o', 'LogLevel=ERROR',
202
                               '-P', remoteData['serverport'],
203
                               '-i', sshkeyfile.name,
204
                               '-b', uploadinstructionsfile.name,
205
                               '%s@%s' % (remoteData['username'], remoteData['serveraddress'])]
206
                print('Uploading bundle to %s as %s with sha256 hash %s' % (remoteData['servername'], remoteFileBundlePathName, hashforbundle))
207
                if subprocess.call(sftpCommand) != 0:
208
                    raise RuntimeError('The sftp command returned non-zero status')
209
210
    print('Done: archive sucesfully uploaded to %s/%s' % (remoteData['baseurl'], remoteFileBundlePathName))
211
    return 0
212
213
214
def main():
215
    parser = optparse.OptionParser('usage: %prog [options]')
216
    parser.add_option('--platform', dest='platform')
217
    parser.add_option('--debug', action='store_const', const='debug', dest='configuration')
218
    parser.add_option('--release', action='store_const', const='release', dest='configuration')
219
    parser.add_option('--revision', action='store', type='string', dest='revision')
220
    parser.add_option('--builder-name', action='store', type='string', dest='buildername')
221
    parser.add_option('--remote-config-file', action='store',  type='string', dest='remoteConfigFile')
222
    options, args = parser.parse_args()
223
224
    if not options.platform:
225
        parser.error('Platform is required')
226
        return 1
227
    if not options.configuration:
228
        parser.error('Configuration is required')
229
        return 1
230
231
    platform = options.platform.lower()
232
    configuration = options.configuration.lower()
233
    if platform == 'gtk':
234
        flatpakutils.run_in_sandbox_if_available(sys.argv)
235
        if not flatpakutils.is_sandboxed():
236
            jhbuildutils.enter_jhbuild_environment_if_available("gtk")
237
    else:
238
        raise NotImplementedError('Unsupported platform')
239
240
    bundleFilePath = createJSCBundle(configuration, options.revision, options.buildername, platform)
241
    print('Bundle file created at: %s' % bundleFilePath)
242
    if options.remoteConfigFile is not None:
243
        if not os.path.isfile(options.remoteConfigFile):
244
            raise ValueError("Can't find remote config file for upload at path %s" % options.remoteConfigFile)
245
        return uploadJSCBundle(bundleFilePath, options.remoteConfigFile, options.configuration, options.revision)
246
    return 0
247
248
249
if __name__ == '__main__':
250
    sys.exit(main())
- a/Tools/jhbuild/jhbuildutils.py -1 / +1 lines
Lines 53-59 def enter_jhbuild_environment_if_available(platform): a/Tools/jhbuild/jhbuildutils.py_sec1
53
    try:
53
    try:
54
        import jhbuild.config
54
        import jhbuild.config
55
        from jhbuild.errors import FatalError
55
        from jhbuild.errors import FatalError
56
        gettext.install('jhbuild', localedir=os.path.join(source_path, 'mo'), unicode=True)
56
        gettext.install('jhbuild', localedir=os.path.join(source_path, 'mo'))
57
        config = jhbuild.config.Config(get_config_file_for_platform(platform), [])
57
        config = jhbuild.config.Config(get_config_file_for_platform(platform), [])
58
    except FatalError as exception:
58
    except FatalError as exception:
59
        sys.stderr.write('Could not load jhbuild config file: %s\n' % exception.args[0])
59
        sys.stderr.write('Could not load jhbuild config file: %s\n' % exception.args[0])

Return to Bug 215266