예제 #1
0
def freeze_mac():
    if not exists(path('target/Icon.icns')):
        _generate_iconset()
        run(['iconutil', '-c', 'icns',
             path('target/Icon.iconset')],
            check=True)
    pyinstaller_args = ['--windowed', '--icon', path('target/Icon.icns')]
    bundle_identifier = SETTINGS.get('mac_bundle_identifier', '')
    if bundle_identifier:
        pyinstaller_args.extend(['--osx-bundle-identifier', bundle_identifier])
    run_pyinstaller(extra_args=pyinstaller_args)
    _remove_unwanted_pyinstaller_files()
    _fix_sparkle_delta_updates()
    generate_resources(
        dest_dir=path('${freeze_dir}'),
        dest_dir_for_base=path('${freeze_dir}/Contents/Resources'))
예제 #2
0
파일: mac.py 프로젝트: l3dlp-sandbox/fbs
def freeze_mac(extra_pyinstaller_args=None, debug=False):
    if extra_pyinstaller_args is None:
        extra_pyinstaller_args = []
    if not exists(path('target/Icon.icns')):
        _generate_iconset()
        run(['iconutil', '-c', 'icns',
             path('target/Icon.iconset')],
            check=True)
    pyinstaller_args = ['--windowed', '--icon', path('target/Icon.icns')]
    bundle_identifier = SETTINGS['mac_bundle_identifier']
    if bundle_identifier:
        pyinstaller_args.extend(['--osx-bundle-identifier', bundle_identifier])
    run_pyinstaller(pyinstaller_args + extra_pyinstaller_args, debug)
    _remove_unwanted_pyinstaller_files()
    _fix_sparkle_delta_updates()
    generate_resources()
예제 #3
0
def release():
    """
    Bump version and run clean,freeze,...,upload
    """
    require_existing_project()
    version = SETTINGS['version']
    next_version = _get_next_version(version)
    release_version = prompt_for_value('Release version', default=next_version)
    activate_profile('release')
    SETTINGS['version'] = release_version
    log_level = _LOG.level
    if log_level == logging.NOTSET:
        _LOG.setLevel(logging.WARNING)
    try:
        clean()
        freeze()
        installer()
        sign_installer()
        repo()
    finally:
        _LOG.setLevel(log_level)
    upload()
    base_json = 'src/build/settings/base.json'
    update_json(path(base_json), {'version': release_version})
    _LOG.info('Also, %s was updated with the new version.', base_json)
예제 #4
0
 def test_freeze_installer(self):
     freeze()
     if is_mac():
         executable = path('${freeze_dir}/Contents/MacOS/${app_name}')
     elif is_windows():
         executable = path('${freeze_dir}/${app_name}.exe')
     else:
         executable = path('${freeze_dir}/${app_name}')
     self.assertTrue(exists(executable), executable + ' does not exist')
     installer()
     self.assertTrue(exists(path('target/${installer}')))
     if is_linux():
         applications_dir = path('target/installer/usr/share/applications')
         self.assertEqual(['MyApp.desktop'], listdir(applications_dir))
         with open(join(applications_dir, 'MyApp.desktop')) as f:
             self.assertIn('MyApp', f.read())
예제 #5
0
def test():
    """
    Execute your automated tests
    """
    sys.path.append(path('src/main/python'))
    suite = TestSuite()
    test_dirs = SETTINGS['test_dirs']
    for test_dir in map(path, test_dirs):
        sys.path.append(test_dir)
        try:
            dir_names = listdir(test_dir)
        except FileNotFoundError:
            continue
        for dir_name in dir_names:
            dir_path = join(test_dir, dir_name)
            if isfile(join(dir_path, '__init__.py')):
                suite.addTest(defaultTestLoader.discover(
                    dir_name, top_level_dir=test_dir
                ))
    has_tests = bool(list(suite))
    if has_tests:
        TextTestRunner().run(suite)
    else:
        print(
            'No tests found. You can add them to:\n * '+
            '\n * '.join(test_dirs)
        )
예제 #6
0
def run():
    """
    Run your app from source
    """
    require_existing_project()
    if not _has_module('PyQt5') and not _has_module('PySide2'):
        raise FbsError("Couldn't find PyQt5 or PySide2. Maybe you need to:\n"
                       "    pip install PyQt5==5.9.2 or\n"
                       "    pip install PySide2==5.12.2")
    env = dict(os.environ)
    pythonpath = path('src/main/python')
    old_pythonpath = env.get('PYTHONPATH', '')
    if old_pythonpath:
        pythonpath += os.pathsep + old_pythonpath
    env['PYTHONPATH'] = pythonpath
    subprocess.run([sys.executable, path(SETTINGS['main_module'])], env=env)
def _add_missing_dlls():
    freeze_dir = path('${freeze_dir}')
    for dll_name in ('msvcr100.dll', 'msvcr110.dll', 'msvcp110.dll',
                     'vcruntime140.dll', 'msvcp140.dll', 'concrt140.dll',
                     'vccorlib140.dll', 'api-ms-win-crt-multibyte-l1-1-0.dll'):
        if not exists(join(freeze_dir, dll_name)):
            copy(join(r'c:\Windows\System32', dll_name), freeze_dir)
예제 #8
0
def release(version=None):
    """
    Bump version and run clean,freeze,...,upload
    """
    require_existing_project()
    if version is None:
        curr_version = SETTINGS['version']
        next_version = _get_next_version(curr_version)
        release_version = prompt_for_value('Release version',
                                           default=next_version)
    elif version == 'current':
        release_version = SETTINGS['version']
    else:
        release_version = version
    activate_profile('release')
    SETTINGS['version'] = release_version
    log_level = _LOG.level
    if log_level == logging.NOTSET:
        _LOG.setLevel(logging.WARNING)
    try:
        clean()
        freeze()
        if is_windows() and _has_windows_codesigning_certificate():
            sign()
        installer()
        if (is_windows() and _has_windows_codesigning_certificate()) or \
            is_arch_linux() or is_fedora():
            sign_installer()
        repo()
    finally:
        _LOG.setLevel(log_level)
    upload()
    base_json = 'src/build/settings/base.json'
    update_json(path(base_json), {'version': release_version})
    _LOG.info('Also, %s was updated with the new version.', base_json)
예제 #9
0
파일: _util.py 프로젝트: mherrmann/fbs
def require_installer():
    installer = path('target/${installer}')
    if not exists(installer):
        raise FbsError(
            'Installer does not exist. Maybe you need to run:\n'
            '    fbs installer'
        )
예제 #10
0
파일: _util.py 프로젝트: mherrmann/fbs
def require_existing_project():
    if not exists(path('src')):
        raise FbsError(
            "Could not find the src/ directory. Are you in the right folder?\n"
            "If yes, did you already run\n"
            "    fbs startproject ?"
        )
예제 #11
0
def run_fpm(output_type):
    dest = path('target/${installer}')
    if exists(dest):
        remove(dest)
    # Lower-case the name to avoid the following fpm warning:
    #  > Debian tools (dpkg/apt) don't do well with packages that use capital
    #  > letters in the name. In some cases it will automatically downcase
    #  > them, in others it will not. It is confusing. Best to not use any
    #  > capital letters at all.
    name = SETTINGS['app_name'].lower()
    args = [
        'fpm', '-s', 'dir',
        # We set the log level to error because fpm prints the following warning
        # even if we don't have anything in /etc:
        #  > Debian packaging tools generally labels all files in /etc as config
        #  > files, as mandated by policy, so fpm defaults to this behavior for
        #  > deb packages. You can disable this default behavior with
        #  > --deb-no-default-config-files flag
        '--log', 'error',
        '-C', path('target/installer'),
        '-n', name,
        '-v', SETTINGS['version'],
        '--vendor', SETTINGS['author'],
        '-t', output_type,
        '-p', dest
    ]
    if SETTINGS['description']:
        args.extend(['--description', SETTINGS['description']])
    if SETTINGS['author_email']:
        args.extend([
            '-m', '%s <%s>' % (SETTINGS['author'], SETTINGS['author_email'])
        ])
    if SETTINGS['url']:
        args.extend(['--url', SETTINGS['url']])
    for dependency in SETTINGS['depends']:
        args.extend(['-d', dependency])
    if is_arch_linux():
        for opt_dependency in SETTINGS['depends_opt']:
            args.extend(['--pacman-optional-depends', opt_dependency])
    try:
        run(args, check=True, stdout=DEVNULL)
    except FileNotFoundError:
        raise FileNotFoundError(
            "fbs could not find executable 'fpm'. Please install fpm using the "
            "instructions at "
            "https://fpm.readthedocs.io/en/latest/installing.html."
        ) from None
예제 #12
0
def create_installer_windows():
    setup_nsi = join(dirname(__file__), 'Setup.nsi')
    copy_with_filtering(setup_nsi,
                        path('target/NSIS'),
                        replacements={
                            'app_name': SETTINGS['app_name'],
                            'author': SETTINGS['author']
                        },
                        files_to_filter=[setup_nsi],
                        placeholder='%%{%s}')
    try:
        run(['makensis', 'Setup.nsi'], cwd=path('target/NSIS'), check=True)
    except FileNotFoundError:
        raise FileNotFoundError(
            "fbs could not find executable 'makensis'. Please install NSIS and "
            "add its installation directory to your PATH environment variable."
        ) from None
예제 #13
0
def _generate_icons():
    dest_root = path('target/installer/usr/share/icons/hicolor')
    makedirs(dest_root)
    icons_fname = '%s.png' % SETTINGS['app_name']
    for size, icon_path in get_icons():
        icon_dest = join(dest_root, '%dx%d' % (size, size), 'apps', icons_fname)
        makedirs(dirname(icon_dest))
        copy(icon_path, icon_dest)
예제 #14
0
 def test_generate_resources(self):
     self.init_fbs('Mac')
     _generate_resources()
     info_plist = path('${freeze_dir}/Contents/Info.plist')
     self.assertTrue(exists(info_plist))
     with open(info_plist) as f:
         self.assertIn('MyApp', f.read(),
                       "Did not replace '${app_name}' by 'MyApp'")
def init_licensing():
    """
    Generate public/private keys for licensing
    """
    require_existing_project()
    try:
        import rsa
    except ImportError:
        _LOG.error('Please install Python library `rsa`. Eg. via:\n'
                   '    pip install rsa')
        return
    nbits = _prompt_for_nbits()
    print('')
    pubkey, privkey = rsa.newkeys(nbits)
    pubkey_args = {'n': pubkey.n, 'e': pubkey.e}
    privkey_args = {
        attr: getattr(privkey, attr)
        for attr in ('n', 'e', 'd', 'p', 'q')
    }
    update_json(path(SECRET_JSON), {
        'licensing_privkey': privkey_args,
        'licensing_pubkey': pubkey_args
    })
    try:
        with open(path(BASE_JSON)) as f:
            user_base_settings = json.load(f)
    except FileNotFoundError:
        user_base_settings = {}
    public_settings = user_base_settings.get('public_settings', [])
    if 'licensing_pubkey' not in public_settings:
        public_settings.append('licensing_pubkey')
        update_json(path(BASE_JSON), {'public_settings': public_settings})
        updated_base_json = True
    else:
        updated_base_json = False
    message = 'Saved a public/private key pair for licensing to:\n    %s.\n' \
              % SECRET_JSON
    if updated_base_json:
        message += 'Also added "licensing_pubkey" to "public_settings" in' \
                   '\n    %s.\n' \
                   '(This lets your app read the public key when it runs.)\n' \
                   % BASE_JSON
    message += '\nFor details on how to implement licensing for your ' \
               'application, see:\n '\
               '    https://build-system.fman.io/manual#licensing.'
    _LOG.info(message)
예제 #16
0
def generate_resources():
    freeze_dir = path('${freeze_dir}')
    if is_mac():
        resources_dest_dir = join(freeze_dir, 'Contents', 'Resources')
    else:
        resources_dest_dir = freeze_dir
    kwargs = {'files_to_filter': SETTINGS['resources_to_filter']}
    resource_dirs = (
        path('src/main/resources/base'),
        path('src/main/resources/' + platform.name().lower())
    )
    for dir_ in resource_dirs:
        if exists(dir_):
            copy_with_filtering(dir_, resources_dest_dir, **kwargs)
        frozen_resources_dir = dir_ + '-frozen'
        if exists(frozen_resources_dir):
            copy_with_filtering(frozen_resources_dir, freeze_dir, **kwargs)
예제 #17
0
def freeze_mac(debug=False):
    if not exists(path('target/Icon.icns')):
        _generate_iconset()
        run(['iconutil', '-c', 'icns',
             path('target/Icon.iconset')],
            check=True)
    args = []
    if not (debug or SETTINGS['show_console_window']):
        args.append('--windowed')
    args.extend(['--icon', path('target/Icon.icns')])
    bundle_identifier = SETTINGS['mac_bundle_identifier']
    if bundle_identifier:
        args.extend(['--osx-bundle-identifier', bundle_identifier])
    run_pyinstaller(args, debug)
    _remove_unwanted_pyinstaller_files()
    _fix_sparkle_delta_updates()
    _generate_resources()
예제 #18
0
def create_repo_ubuntu():
    dest_dir = path('target/repo')
    tmp_dir = path('target/repo-tmp')
    if exists(dest_dir):
        rmtree(dest_dir)
    if exists(tmp_dir):
        rmtree(tmp_dir)
    makedirs(tmp_dir)
    distr_file = 'src/repo/ubuntu/distributions'
    distr_path = path(distr_file)
    if not exists(distr_path):
        distr_path = default_path(distr_file)
    copy_with_filtering(distr_path, tmp_dir, files_to_filter=[distr_path])
    preset_gpg_passphrase()
    check_call([
        'reprepro', '-b', dest_dir, '--confdir', tmp_dir,
        'includedeb', 'stable', path('target/${installer}')
    ], stdout=DEVNULL)
예제 #19
0
def sign_installer_arch():
    installer = path('target/${installer}')
    # Prevent GPG from prompting us for the passphrase when signing:
    preset_gpg_passphrase()
    check_call([
        'gpg', '--batch', '--yes', '-u', SETTINGS['gpg_key'], '--output',
        installer + '.sig', '--detach-sig', installer
    ],
               stdout=DEVNULL)
예제 #20
0
def run_pyinstaller(extra_args=None, debug=False):
    if extra_args is None:
        extra_args = []
    app_name = SETTINGS['app_name']
    log_level = 'DEBUG' if debug else 'WARN'
    cmdline = [
        'pyinstaller', '--name', app_name, '--noupx', '--log-level', log_level
    ] + extra_args + [
        '--distpath',
        path('target'), '--specpath',
        path('target/PyInstaller'), '--workpath',
        path('target/PyInstaller'), SETTINGS['main_module']
    ]
    if debug:
        cmdline.append('--debug')
    run(cmdline, check=True)
    output_dir = path('target/' + app_name + ('.app' if is_mac() else ''))
    rename(output_dir, path('${freeze_dir}'))
예제 #21
0
def create_repo_fedora():
    if exists(path('target/repo')):
        rmtree(path('target/repo'))
    makedirs(path('target/repo/${version}'))
    copy(path('target/${installer}'), path('target/repo/${version}'))
    check_call(['createrepo_c', '.'],
               cwd=(path('target/repo')),
               stdout=DEVNULL)
    repo_file = path('src/repo/fedora/${app_name}.repo')
    use_default = not exists(repo_file)
    if use_default:
        repo_file = default_path('src/repo/fedora/AppName.repo')
    copy_with_filtering(repo_file,
                        path('target/repo'),
                        files_to_filter=[repo_file])
    if use_default:
        rename(path('target/repo/AppName.repo'),
               path('target/repo/${app_name}.repo'))
예제 #22
0
def generate_resources(dest_dir=None, dest_dir_for_base=None, exclude=None):
    if dest_dir is None:
        # Set this default here instead of in the function definition
        # (`def generate_resources(dest_dir=path(...) ...)`) because we can't
        # call path(...) at the module level.
        dest_dir = path('target/resources')
    if dest_dir_for_base is None:
        dest_dir_for_base = dest_dir
    if exclude is None:
        exclude = []
    resources_to_filter = SETTINGS['resources_to_filter']
    kwargs = {'exclude': exclude, 'files_to_filter': resources_to_filter}
    copy_with_filtering(
        path('src/main/resources/base'), dest_dir_for_base, **kwargs
    )
    os_resources_dir = path('src/main/resources/' + platform.name().lower())
    if exists(os_resources_dir):
        copy_with_filtering(os_resources_dir, dest_dir, **kwargs)
예제 #23
0
def get_icons():
    result = {}
    for icons_dir in (
        'src/main/icons/base', 'src/main/icons/' + platform.name().lower()
    ):
        for icon_path in glob(path(icons_dir + '/*.png')):
            size = int(splitext(basename(icon_path))[0])
            result[size] = icon_path
    return list(result.items())
예제 #24
0
def gengpgkey():
    """
    Generate a GPG key for Linux code signing
    """
    require_existing_project()
    if exists(_DEST_DIR):
        raise FbsError('The %s folder already exists. Aborting.' % _DEST_DIR)
    try:
        email = prompt_for_value('Email address')
        name = prompt_for_value('Real name', default=SETTINGS['author'])
        passphrase = prompt_for_value('Key password', password=True)
    except KeyboardInterrupt:
        print('')
        return
    print('')
    _LOG.info('Generating the GPG key. This can take a little...')
    _init_docker()
    args = ['run', '-t']
    if exists('/dev/urandom'):
        # Give the key generator more entropy on Posix:
        args.extend(['-v', '/dev/urandom:/dev/random'])
    args.extend([_DOCKER_IMAGE, '/root/genkey.sh', name, email, passphrase])
    result = _run_docker(args,
                         check=True,
                         stdout=PIPE,
                         universal_newlines=True)
    key = _snip(
        result.stdout,
        "revocation certificate stored as '/root/.gnupg/openpgp-revocs.d/",
        ".rev'",
        include_bounds=False)
    pubkey = _snip(result.stdout, '-----BEGIN PGP PUBLIC KEY BLOCK-----\n',
                   '-----END PGP PUBLIC KEY BLOCK-----\n')
    privkey = _snip(result.stdout, '-----BEGIN PGP PRIVATE KEY BLOCK-----\n',
                    '-----END PGP PRIVATE KEY BLOCK-----\n')
    makedirs(path(_DEST_DIR), exist_ok=True)
    pubkey_dest = _DEST_DIR + '/' + _PUBKEY_NAME
    Path(path(pubkey_dest)).write_text(pubkey)
    Path(path(_DEST_DIR + '/' + _PRIVKEY_NAME)).write_text(privkey)
    update_json(path(BASE_JSON), {'gpg_key': key, 'gpg_name': name})
    update_json(path(SECRET_JSON), {'gpg_pass': passphrase})
    _LOG.info(
        'Done. Created %s and ...%s. Also updated %s and ...secret.json with '
        'the values you provided.', pubkey_dest, _PRIVKEY_NAME, BASE_JSON)
예제 #25
0
def update_json(f_path, dict_):
    f = Path(f_path)
    try:
        contents = f.read_text()
    except FileNotFoundError:
        indent = _infer_indent(Path(path(BASE_JSON)).read_text())
        new_contents = json.dumps(dict_, indent=indent)
    else:
        new_contents = _update_json_str(contents, dict_)
    f.write_text(new_contents)
예제 #26
0
파일: __init__.py 프로젝트: hhy5277/fbs
def clean():
    """
    Remove previous build outputs
    """
    try:
        rmtree(path('target'))
    except FileNotFoundError:
        return
    except OSError:
        # In a docker container, target/ may be mounted so we can't delete it.
        # Delete its contents instead:
        for f in listdir(path('target')):
            fpath = join(path('target'), f)
            if isdir(fpath):
                rmtree(fpath, ignore_errors=True)
            elif isfile(fpath):
                remove(fpath)
            elif islink(fpath):
                unlink(fpath)
예제 #27
0
def installer():
    """
    Create an installer for your app
    """
    require_existing_project()
    if not exists(path('${freeze_dir}')):
        raise FbsError(
            'It seems your app has not yet been frozen. Please run:\n'
            '    fbs freeze')
    linux_distribution_not_supported_msg = \
        "Your Linux distribution is not supported, sorry. " \
        "You can run `fbs buildvm` followed by `fbs runvm` to start a Docker " \
        "VM of a supported distribution."
    try:
        installer_fname = SETTINGS['installer']
    except KeyError:
        if is_linux():
            raise FbsError(linux_distribution_not_supported_msg)
        raise
    out_file = join('target', installer_fname)
    msg_parts = ['Created %s.' % out_file]
    if is_windows():
        from fbs.installer.windows import create_installer_windows
        create_installer_windows()
    elif is_mac():
        from fbs.installer.mac import create_installer_mac
        create_installer_mac()
    elif is_linux():
        app_name = SETTINGS['app_name']
        if is_ubuntu():
            from fbs.installer.ubuntu import create_installer_ubuntu
            create_installer_ubuntu()
            install_cmd = 'sudo dpkg -i ' + out_file
            remove_cmd = 'sudo dpkg --purge ' + app_name
        elif is_arch_linux():
            from fbs.installer.arch import create_installer_arch
            create_installer_arch()
            install_cmd = 'sudo pacman -U ' + out_file
            remove_cmd = 'sudo pacman -R ' + app_name
        elif is_fedora():
            from fbs.installer.fedora import create_installer_fedora
            create_installer_fedora()
            install_cmd = 'sudo dnf install ' + out_file
            remove_cmd = 'sudo dnf remove ' + app_name
        else:
            raise FbsError(linux_distribution_not_supported_msg)
        msg_parts.append(
            'You can for instance install it via the following command:\n'
            '    %s\n'
            'This places it in /opt/%s. To uninstall it again, you can use:\n'
            '    %s' % (install_cmd, app_name, remove_cmd))
    else:
        raise FbsError('Unsupported OS')
    _LOG.info(' '.join(msg_parts))
예제 #28
0
파일: __init__.py 프로젝트: hhy5277/fbs
def run():
    """
    Run your app from source
    """
    env = dict(os.environ)
    pythonpath = path('src/main/python')
    old_pythonpath = env.get('PYTHONPATH', '')
    if old_pythonpath:
        pythonpath += os.pathsep + old_pythonpath
    env['PYTHONPATH'] = pythonpath
    subprocess.run([sys.executable, SETTINGS['main_module']], env=env)
예제 #29
0
def generate_resources():
    """
    Copy the data files from src/main/resources to the target/ directory.
    Automatically filters files mentioned in the setting resources_to_filter:
    Placeholders such as ${app_name} are automatically replaced by the
    corresponding setting in files on that list.
    """
    freeze_dir = path('${freeze_dir}')
    if is_mac():
        resources_dest_dir = join(freeze_dir, 'Contents', 'Resources')
    else:
        resources_dest_dir = freeze_dir
    kwargs = {'files_to_filter': SETTINGS['resources_to_filter']}
    resource_dirs = (path('src/main/resources/base'),
                     path('src/main/resources/' + platform.name().lower()))
    for dir_ in resource_dirs:
        if exists(dir_):
            copy_with_filtering(dir_, resources_dest_dir, **kwargs)
        frozen_resources_dir = dir_ + '-frozen'
        if exists(frozen_resources_dir):
            copy_with_filtering(frozen_resources_dir, freeze_dir, **kwargs)
예제 #30
0
def get_icons():
    """
    Return a list [(size_in_pixels, path)] of available app icons for the
    current platform.
    """
    result = {}
    for icons_dir in ('src/main/icons/base',
                      'src/main/icons/' + platform.name().lower()):
        for icon_path in glob(path(icons_dir + '/*.png')):
            size = int(splitext(basename(icon_path))[0])
            result[size] = icon_path
    return list(result.items())