Skip to content

Commit

Permalink
Implement ApplicationContext#public_settings
Browse files Browse the repository at this point in the history
This lets you access some build settings at runtime; Eg.
public_settings['version']. What is available is controlled by the new
setting "public_settings".
  • Loading branch information
mherrmann committed Dec 21, 2018
1 parent f660041 commit 5289685
Show file tree
Hide file tree
Showing 19 changed files with 218 additions and 128 deletions.
44 changes: 13 additions & 31 deletions fbs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from fbs import _state, _defaults
from fbs._settings import load_settings, expand_placeholders
from fbs import _state
from fbs._state import LOADED_PROFILES
from fbs_runtime import platform, FbsError
from fbs_runtime.platform import is_ubuntu, is_linux, is_arch_linux, is_fedora
from os.path import normpath, join, exists, abspath
from fbs_runtime import FbsError, _source
from fbs_runtime._fbs import get_core_settings, get_default_profiles
from fbs_runtime._settings import load_settings, expand_placeholders
from fbs_runtime._source import get_settings_paths
from os.path import abspath

"""
fbs populates SETTINGS with the current build settings. A typical example is
Expand All @@ -16,8 +17,8 @@ def init(project_dir):
Call this if you are invoking neither `fbs` on the command line nor
fbs.cmdline.main() from Python.
"""
SETTINGS['project_dir'] = abspath(project_dir)
for profile in _get_default_profiles():
SETTINGS.update(get_core_settings(abspath(project_dir)))
for profile in get_default_profiles():
activate_profile(profile)

def activate_profile(profile_name):
Expand All @@ -29,13 +30,10 @@ def activate_profile(profile_name):
production server URL instead of a staging server.
"""
LOADED_PROFILES.append(profile_name)
json_paths = [
path_fn('src/build/settings/%s.json' % profile)
for path_fn in (_defaults.path, path)
for profile in LOADED_PROFILES
]
base_settings = {'project_dir': SETTINGS['project_dir']}
SETTINGS.update(load_settings(filter(exists, json_paths), base_settings))
project_dir = SETTINGS['project_dir']
json_paths = get_settings_paths(project_dir, LOADED_PROFILES)
core_settings = get_core_settings(project_dir)
SETTINGS.update(load_settings(json_paths, core_settings))

def path(path_str):
"""
Expand All @@ -51,20 +49,4 @@ def path(path_str):
error_message = "Cannot call path(...) until fbs.init(...) has been " \
"called."
raise FbsError(error_message) from None
return normpath(join(project_dir, *path_str.split('/')))

def _get_default_profiles():
yield 'base'
# The "secret" profile lets the user store sensitive settings such as
# passwords in src/build/settings/secret.json. When using Git, the user can
# exploit this by adding secret.json to .gitignore, thus preventing it from
# being uploaded to services such as GitHub.
yield 'secret'
yield platform.name().lower()
if is_linux():
if is_ubuntu():
yield 'ubuntu'
elif is_arch_linux():
yield 'arch'
elif is_fedora():
yield 'fedora'
return _source.path(project_dir, path_str)
4 changes: 0 additions & 4 deletions fbs/_defaults/__init__.py

This file was deleted.

1 change: 1 addition & 0 deletions fbs/_defaults/src/build/settings/base.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"src/build/docker/fedora/.rpmmacros"
],
"hidden_imports": [],
"public_settings": ["app_name", "author", "version"],
"docker_images": {
"ubuntu": {
"build_files": ["requirements/", "src/sign/linux/"],
Expand Down
3 changes: 2 additions & 1 deletion fbs/_defaults/src/build/settings/linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"depends": [],
"files_to_filter": [
"src/installer/linux/usr/share/applications/AppName.desktop"
]
],
"public_settings": ["categories", "description", "author_email", "url"]
}
9 changes: 5 additions & 4 deletions fbs/builtin_commands/_docker.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from fbs import path, SETTINGS, _defaults
from fbs import path, SETTINGS
from fbs.builtin_commands import require_existing_project
from fbs.cmdline import command
from fbs.resources import _copy
from fbs_runtime import FbsError
from fbs_runtime._source import default_path
from os import listdir
from os.path import exists
from shutil import rmtree
Expand All @@ -24,7 +25,7 @@ def buildvm(name):
if exists(build_dir):
rmtree(build_dir)
src_root = 'src/build/docker'
available_vms = set(listdir(_defaults.path(src_root)))
available_vms = set(listdir(default_path(src_root)))
if exists(path(src_root)):
available_vms.update(listdir(path(src_root)))
if name not in available_vms:
Expand All @@ -33,10 +34,10 @@ def buildvm(name):
(name, ''.join(['\n * ' + vm for vm in available_vms]))
)
src_dir = src_root + '/' + name
for path_fn in _defaults.path, path:
for path_fn in default_path, path:
_copy(path_fn, src_dir, build_dir)
settings = SETTINGS['docker_images'].get(name, {})
for path_fn in _defaults.path, path:
for path_fn in default_path, path:
for p in settings.get('build_files', []):
_copy(path_fn, p, build_dir)
args = ['build', '--pull', '-t', _get_docker_id(name), build_dir]
Expand Down
31 changes: 28 additions & 3 deletions fbs/freeze/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
from fbs import path, SETTINGS, _defaults
from fbs import path, SETTINGS
from fbs._state import LOADED_PROFILES
from fbs.resources import _copy
from fbs_runtime._fbs import get_public_settings
from fbs_runtime._source import default_path
from fbs_runtime.platform import is_mac
from os import rename
from os.path import join
from pathlib import PurePath
from subprocess import run
from tempfile import TemporaryDirectory

import fbs_runtime._frozen

def run_pyinstaller(extra_args=None, debug=False):
if extra_args is None:
Expand Down Expand Up @@ -33,14 +38,34 @@ def run_pyinstaller(extra_args=None, debug=False):
])
if debug:
args.append('--debug')
run(args, check=True)
with _PyInstallerRuntimehook() as hook_path:
args.extend(['--runtime-hook', hook_path])
run(args, check=True)
output_dir = path('target/' + app_name + ('.app' if is_mac() else ''))
freeze_dir = path('${freeze_dir}')
# In most cases, rename(src, dst) silently "works" when src == dst. But on
# some Windows drives, it raises a FileExistsError. So check src != dst:
if PurePath(output_dir) != PurePath(freeze_dir):
rename(output_dir, freeze_dir)

class _PyInstallerRuntimehook:
def __init__(self):
self._tmp_dir = TemporaryDirectory()
def __enter__(self):
module = fbs_runtime._frozen
hook_path = join(self._tmp_dir.name, 'fbs_pyinstaller_hook.py')
with open(hook_path, 'w') as f:
# Inject public settings such as "version" into the binary, so
# they're available at run time:
f.write('\n'.join([
'import importlib',
'module = importlib.import_module(%r)' % module.__name__,
'module.PUBLIC_SETTINGS = %r' % get_public_settings(SETTINGS)
]))
return hook_path
def __exit__(self, *_):
self._tmp_dir.cleanup()

def _generate_resources():
"""
Copy the data files from src/main/resources to ${freeze_dir}.
Expand All @@ -53,7 +78,7 @@ def _generate_resources():
resources_dest_dir = join(freeze_dir, 'Contents', 'Resources')
else:
resources_dest_dir = freeze_dir
for path_fn in _defaults.path, path:
for path_fn in default_path, path:
for profile in LOADED_PROFILES:
_copy(path_fn, 'src/main/resources/' + profile, resources_dest_dir)
_copy(path_fn, 'src/freeze/' + profile, freeze_dir)
5 changes: 3 additions & 2 deletions fbs/installer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from fbs import _defaults, path, LOADED_PROFILES
from fbs import path, LOADED_PROFILES
from fbs.resources import _copy
from fbs_runtime._source import default_path

def _generate_installer_resources():
for path_fn in _defaults.path, path:
for path_fn in default_path, path:
for profile in LOADED_PROFILES:
_copy(path_fn, 'src/installer/' + profile, path('target/installer'))
5 changes: 3 additions & 2 deletions fbs/repo/fedora.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from fbs import path, _defaults
from fbs import path
from fbs.resources import copy_with_filtering
from fbs_runtime._source import default_path
from os import makedirs, rename
from os.path import exists
from shutil import rmtree, copy
Expand All @@ -14,7 +15,7 @@ def create_repo_fedora():
repo_file = path('src/repo/fedora/${app_name}.repo')
use_default = not exists(repo_file)
if use_default:
repo_file = _defaults.path('src/repo/fedora/AppName.repo')
repo_file = default_path('src/repo/fedora/AppName.repo')
copy_with_filtering(
repo_file, path('target/repo'), files_to_filter=[repo_file]
)
Expand Down
5 changes: 3 additions & 2 deletions fbs/repo/ubuntu.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from fbs import path, _defaults
from fbs import path
from fbs._gpg import preset_gpg_passphrase
from fbs.resources import copy_with_filtering
from fbs_runtime._source import default_path
from os import makedirs
from os.path import exists
from shutil import rmtree
Expand All @@ -17,7 +18,7 @@ def create_repo_ubuntu():
distr_file = 'src/repo/ubuntu/distributions'
distr_path = path(distr_file)
if not exists(distr_path):
distr_path = _defaults.path(distr_file)
distr_path = default_path(distr_file)
copy_with_filtering(distr_path, tmp_dir, files_to_filter=[distr_path])
preset_gpg_passphrase()
check_call([
Expand Down
68 changes: 24 additions & 44 deletions fbs_runtime/_fbs.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,27 @@
from fbs_runtime import platform, FbsError
from fbs_runtime.platform import is_mac
from os.path import join, exists, realpath, dirname, pardir
from pathlib import PurePath
from fbs_runtime import platform
from fbs_runtime.platform import is_ubuntu, is_linux, is_arch_linux, is_fedora

import errno
import inspect
import os
import sys
def get_core_settings(project_dir):
return {
'project_dir': project_dir
}

class ResourceLocator:
def __init__(self, resource_dirs):
self._dirs = resource_dirs
def locate(self, *rel_path):
for resource_dir in self._dirs:
resource_path = join(resource_dir, *rel_path)
if exists(resource_path):
return realpath(resource_path)
raise FileNotFoundError(
errno.ENOENT, 'Could not locate resource', os.sep.join(rel_path)
)
def get_default_profiles():
result = ['base']
# The "secret" profile lets the user store sensitive settings such as
# passwords in src/build/settings/secret.json. When using Git, the user can
# exploit this by adding secret.json to .gitignore, thus preventing it from
# being uploaded to services such as GitHub.
result.append('secret')
result.append(platform.name().lower())
if is_linux():
if is_ubuntu():
result.append('ubuntu')
elif is_arch_linux():
result.append('arch')
elif is_fedora():
result.append('fedora')
return result

def get_resource_dirs_frozen():
app_dir = dirname(sys.executable)
return [join(app_dir, pardir, 'Resources') if is_mac() else app_dir]

def get_resource_dirs_source(appctxt_cls):
project_dir = _get_project_base_dir(appctxt_cls)
resources_dir = join(project_dir, 'src', 'main', 'resources')
return [
join(resources_dir, platform.name().lower()),
join(resources_dir, 'base'),
join(project_dir, 'src', 'main', 'icons')
]

def _get_project_base_dir(appctxt_cls):
class_file = inspect.getfile(appctxt_cls)
p = PurePath(class_file)
while p != p.parent:
parent_names = [p.parents[2].name, p.parents[1].name, p.parent.name]
if parent_names == ['src', 'main', 'python']:
return str(p.parents[3])
p = p.parent
raise FbsError(
'Could not determine project base directory for %s. Is it in '
'src/main/python?' % appctxt_cls
)
def get_public_settings(settings):
return {k: settings[k] for k in settings['public_settings']}
20 changes: 20 additions & 0 deletions fbs_runtime/_frozen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
This module contains functions that should only be called when running the
frozen form of the app.
"""

from os.path import dirname, join, pardir
from fbs_runtime.platform import is_mac

import sys

# The contents of this dictionary are injected via a PyInstaller runtime hook.
# See: `fbs.freeze._PyInstallerRuntimehook`.
PUBLIC_SETTINGS = {}

def get_resource_dirs():
app_dir = dirname(sys.executable)
return [join(app_dir, pardir, 'Resources') if is_mac() else app_dir]

def load_public_settings():
return PUBLIC_SETTINGS
16 changes: 16 additions & 0 deletions fbs_runtime/_resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from os.path import join, exists, realpath

import errno
import os

class ResourceLocator:
def __init__(self, resource_dirs):
self._dirs = resource_dirs
def locate(self, *rel_path):
for resource_dir in self._dirs:
resource_path = join(resource_dir, *rel_path)
if exists(resource_path):
return realpath(resource_path)
raise FileNotFoundError(
errno.ENOENT, 'Could not locate resource', os.sep.join(rel_path)
)
File renamed without changes.
Loading

0 comments on commit 5289685

Please sign in to comment.