Rename template to builder

parent 9703beaf
import os
import glob
import shutil
from .base import Builder
from clickable.config import Config
from clickable.exceptions import ClickableException
from clickable.utils import find
rust_arch_target_mapping = {
'amd64': 'x86_64-unknown-linux-gnu',
'armhf': 'armv7-unknown-linux-gnueabihf',
'arm64': 'aarch64-unknown-linux-gnu',
}
class RustBuilder(Builder):
name = Config.RUST
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.paths_to_ignore = self._find_click_assets()
self.paths_to_ignore.extend([
# Click stuff
os.path.abspath(self.config.install_dir),
os.path.abspath(self.config.build_dir),
os.path.abspath(self._get_base_build_dir()),
'clickable.json',
os.path.abspath(os.path.join(self.config.cwd, 'Cargo.toml')),
os.path.abspath(os.path.join(self.config.cwd, 'Cargo.lock')),
os.path.abspath(os.path.join(self.config.cwd, 'target')),
])
self.paths_to_ignore.extend(self.config.ignore)
def _get_base_build_dir(self):
base_build_dir = self.config.build_dir
if self.config.arch_triplet in base_build_dir:
base_build_dir = base_build_dir.split(self.config.arch_triplet)[0]
return base_build_dir
@property
def _cargo_target(self):
if self.config.build_arch not in rust_arch_target_mapping:
raise ClickableException(
'Arch {} unsupported by rust template'.format(self.config.build_arch))
return rust_arch_target_mapping[self.config.build_arch]
def _find_click_assets(self):
return [
find(['manifest.json'], self.config.cwd, ignore_dir=self._get_base_build_dir()),
find(['.apparmor'], self.config.cwd, ignore_dir=self._get_base_build_dir(), extensions_only=True),
find(['.desktop'], self.config.cwd, ignore_dir=self._get_base_build_dir(), extensions_only=True),
]
def _ignore(self, path, contents):
ignored = []
for content in contents:
abs_path = os.path.abspath(os.path.join(path, content))
if (
abs_path in self.paths_to_ignore
or content in self.paths_to_ignore
or os.path.splitext(content)[1] == '.rs'
):
ignored += [content]
return ignored
def build(self):
# Copy project assets
#shutil.copytree(self.config.cwd,
# self.config.install_dir, ignore=self._ignore)
# Copy click assets
target_dir = self.config.app_bin_dir
os.makedirs(target_dir, exist_ok=True)
assets = self._find_click_assets()
for asset in assets:
shutil.copy2(asset, self.config.install_dir)
# Build using cargo
cargo_command = 'cargo build {} --target {} --target-dir {}' \
.format('--release' if not self.config.debug_build else '',
self._cargo_target, self.config.build_dir)
self.config.container.run_command(cargo_command)
# There could be more than one executable
executables = glob.glob(os.path.join(
self.config.build_dir,
self._cargo_target,
'debug' if self.config.debug_build else 'release',
"*"))
for filename in filter(lambda f: os.path.isfile(f), executables):
shutil.copy2(filename, '{}/{}'.format(
target_dir,
os.path.basename(filename)),
)
......@@ -55,14 +55,14 @@ class PureBuilder(Builder):
class PythonBuilder(PureBuilder):
# The only difference between this and the Pure template is that this doesn't force the "all" arch
# The only difference between this and the Pure builder is that this doesn't force the "all" arch
name = Constants.PYTHON
def build(self):
logger.warn('The "python" build template is deprecated, please use "precompiled" instead')
logger.warn('The "python" builder is deprecated, please use "precompiled" instead')
super().build()
class PrecompiledBuilder(PureBuilder):
# The only difference between this and the Pure template is that this doesn't force the "all" arch
# The only difference between this and the Pure builder is that this doesn't force the "all" arch
name = Constants.PRECOMPILED
......@@ -47,7 +47,7 @@ class RustBuilder(Builder):
def _cargo_target(self):
if self.config.build_arch not in rust_arch_target_mapping:
raise ClickableException(
'Arch {} unsupported by rust template'.format(self.config.build_arch))
'Arch {} unsupported by rust builder'.format(self.config.build_arch))
return rust_arch_target_mapping[self.config.build_arch]
def _find_click_assets(self):
......
......@@ -45,7 +45,7 @@ class BuildCommand(Command):
def build(self):
builder_classes = get_builders()
builder = builder_classes[self.config.template](self.config, self.device)
builder = builder_classes[self.config.builder](self.config, self.device)
builder.build()
def install_files(self, pattern, dest_dir):
......
......@@ -54,5 +54,5 @@ class LibBuildCommand(Command):
def build(self, lib):
builder_classes = get_builders()
builder = builder_classes[lib.template](lib, None)
builder = builder_classes[lib.builder](lib, None)
builder.build()
......@@ -11,9 +11,9 @@ class GoSupport(DockerSupport):
self.config = config
def update(self, docker_config: DockerConfig):
template = self.config.template
builder = self.config.builder
if template == Constants.GO:
if builder == Constants.GO:
go_paths = list(map(
lambda gopath:
'/gopath/path{}'.format(gopath),
......
......@@ -13,9 +13,9 @@ class RustSupport(DockerSupport):
self.config = config
def update(self, docker_config: DockerConfig):
template = self.config.template
builder = self.config.builder
if template == Constants.RUST:
if builder == Constants.RUST:
cargo_home = self.config.cargo_home
cargo_registry = os.path.join(cargo_home, 'registry')
cargo_git = os.path.join(cargo_home, 'git')
......
......@@ -27,6 +27,22 @@
"all"
]
},
"builder": {
"type": "string",
"enum": [
"pure-qml-qmake",
"qmake",
"pure-qml-cmake",
"cmake",
"custom",
"cordova",
"pure",
"python",
"go",
"rust",
"precompiled"
]
},
"template": {
"type": "string",
"enum": [
......@@ -170,6 +186,14 @@
"type": ["string","array"],
"items": {"type": "string"}
},
"builder": {
"type": "string",
"enum": [
"cmake",
"qmake",
"custom"
]
},
"template": {
"type": "string",
"enum": [
......@@ -191,7 +215,6 @@
}
}
},
"required": ["template"],
"additionalProperties": false
}
}
......
......@@ -13,8 +13,8 @@ class Constants(object):
RUST = 'rust'
PRECOMPILED = 'precompiled'
templates = [PURE_QML_QMAKE, QMAKE, PURE_QML_CMAKE, CMAKE, CUSTOM, CORDOVA, PURE, PYTHON, GO, RUST, PRECOMPILED]
arch_agnostic_templates = [PURE_QML_QMAKE, PURE_QML_CMAKE, PURE]
builders = [PURE_QML_QMAKE, QMAKE, PURE_QML_CMAKE, CMAKE, CUSTOM, CORDOVA, PURE, PYTHON, GO, RUST, PRECOMPILED]
arch_agnostic_builders = [PURE_QML_QMAKE, PURE_QML_CMAKE, PURE]
container_mapping = {
"x86_64": {
......
......@@ -28,13 +28,13 @@ class ProjectFiles(object):
return desktop
class InstallFiles(object):
def __init__(self, install_dir, template, arch):
def __init__(self, install_dir, builder, arch):
self.install_dir = install_dir
self.template = template
self.builder = builder
self.arch = arch
def find_version(self):
if self.template == Constants.CORDOVA:
if self.builder == Constants.CORDOVA:
tree = ElementTree.parse('config.xml')
root = tree.getroot()
version = root.attrib['version'] if 'version' in root.attrib else '1.0.0'
......@@ -44,7 +44,7 @@ class InstallFiles(object):
return version
def find_package_name(self):
if self.template == Constants.CORDOVA:
if self.builder == Constants.CORDOVA:
tree = ElementTree.parse('config.xml')
root = tree.getroot()
package = root.attrib['id'] if 'id' in root.attrib else None
......@@ -61,7 +61,7 @@ class InstallFiles(object):
return package
def find_package_title(self):
if self.template == Constants.CORDOVA:
if self.builder == Constants.CORDOVA:
tree = ElementTree.parse('config.xml')
root = tree.getroot()
title = root.attrib['name'] if 'name' in root.attrib else None
......
......@@ -32,11 +32,11 @@ class LibConfig(object):
path_keys = ['root_dir', 'build_dir', 'src_dir', 'install_dir',
'build_home']
required = ['template']
required = ['builder']
flexible_lists = ['dependencies_host', 'dependencies_target',
'dependencies_ppa', 'dependencies_build',
'build_args', 'make_args']
templates = [Constants.QMAKE, Constants.CMAKE, Constants.CUSTOM]
builders = [Constants.QMAKE, Constants.CMAKE, Constants.CUSTOM]
first_docker_info = True
container_mode = False
......@@ -57,6 +57,7 @@ class LibConfig(object):
'arch': arch,
'arch_triplet': None,
'template': None,
'builder': None,
'postmake': None,
'prebuild': None,
'build': None,
......@@ -78,6 +79,12 @@ class LibConfig(object):
'image_setup': {},
}
# TODO remove support for deprecated "template" in clickable.json
if "template" in json_config:
logger.warning('Parameter "template" is deprecated in clickable.json. Use "builder" as drop-in replacement instead.')
json_config["builder"] = json_config["template"]
json_config["template"] = None
self.config.update(json_config)
if self.config["docker_image"]:
self.is_custom_docker_image = True
......@@ -168,9 +175,13 @@ class LibConfig(object):
self.config[key] = flexible_string_to_list(self.config[key])
def check_config_errors(self):
if self.config['template'] == Constants.CUSTOM and not self.config['build']:
if not self.config['builder']:
raise ClickableException(
'The clickable.json is missing a "builder" in library "{}".'.format(self.config["name"]))
if self.config['builder'] == Constants.CUSTOM and not self.config['build']:
raise ClickableException(
'When using the "custom" template you must specify a "build" in one the lib configs')
'When using the "custom" builder you must specify a "build" in one the lib configs')
if self.is_custom_docker_image:
if self.dependencies_host or self.dependencies_target or self.dependencies_ppa:
......
......@@ -28,7 +28,7 @@ class ProjectConfig(object):
ENV_MAP = {
'CLICKABLE_ARCH': 'restrict_arch_env',
'CLICKABLE_TEMPLATE': 'template',
'CLICKABLE_BUILDER': 'builder',
'CLICKABLE_BUILD_DIR': 'build_dir',
'CLICKABLE_DEFAULT': 'default',
'CLICKABLE_MAKE_JOBS': 'make_jobs',
......@@ -100,7 +100,7 @@ class ProjectConfig(object):
self.set_default_config()
self.parse_configs(args, commands)
self.set_template_interactive()
self.set_builder_interactive()
self.set_conditional_defaults()
self.setup()
self.check_config_errors()
......@@ -113,6 +113,7 @@ class ProjectConfig(object):
'restrict_arch': None,
'arch_triplet': None,
'template': None,
'builder': None,
'postmake': None,
'prebuild': None,
'build': None,
......@@ -157,11 +158,17 @@ class ProjectConfig(object):
json_config = self.load_json_config(config_path)
# TODO remove support for deprecated "arch" in clickable.json
if json_config.get("arch", None):
if "arch" in json_config:
logger.warning('Parameter "arch" is deprecated in clickable.json. Use "restricted_arch" instead.')
json_config["restrict_arch"] = json_config["arch"]
json_config["arch"] = None
# TODO remove support for deprecated "template" in clickable.json
if "template" in json_config:
logger.warning('Parameter "template" is deprecated in clickable.json. Use "builder" as drop-in replacement instead.')
json_config["builder"] = json_config["template"]
json_config["template"] = None
self.config.update(json_config)
env_config = self.load_env_config()
self.config.update(env_config)
......@@ -198,7 +205,7 @@ class ProjectConfig(object):
if not self.config["arch"]:
if self.is_arch_agnostic():
self.config["arch"] = "all"
logger.debug('Architecture set to "all" because template "{}" is architecture agnostic'.format(self.config['template']))
logger.debug('Architecture set to "all" because builder "{}" is architecture agnostic'.format(self.config['builder']))
elif self.is_desktop_mode():
self.config["arch"] = "amd64"
logger.debug('Architecture set to "amd64" because of desktop mode.')
......@@ -227,9 +234,9 @@ class ProjectConfig(object):
raise ClickableException('Clickable currently does not have docker images for your host architecture "{}"'.format(self.host_arch))
if not self.config['kill']:
if self.config['template'] == Constants.CORDOVA:
if self.config['builder'] == Constants.CORDOVA:
self.config['kill'] = 'cordova-ubuntu'
elif self.config['template'] == Constants.PURE_QML_CMAKE or self.config['template'] == Constants.PURE_QML_QMAKE or self.config['template'] == Constants.PURE:
elif self.config['builder'] == Constants.PURE_QML_CMAKE or self.config['builder'] == Constants.PURE_QML_QMAKE or self.config['builder'] == Constants.PURE:
self.config['kill'] = 'qmlscene'
else:
try:
......@@ -261,11 +268,11 @@ class ProjectConfig(object):
def setup_helpers(self):
self.install_files = InstallFiles(
self.config['install_dir'],
self.config['template'],
self.config['builder'],
self.config['arch'])
def is_arch_agnostic(self):
return self.config["template"] in Constants.arch_agnostic_templates
return self.config["builder"] in Constants.arch_agnostic_builders
def __getattr__(self, name):
return self.config[name]
......@@ -548,7 +555,7 @@ class ProjectConfig(object):
logger.warning('"dependencies_build" is deprecated. Use "dependencies_host" instead!')
def is_arch_agnostic(self):
return self.config["template"] in Constants.arch_agnostic_templates
return self.config["builder"] in Constants.arch_agnostic_builders
def is_desktop_mode(self):
return bool(set(['desktop', 'ide', 'test']).intersection(self.commands))
......@@ -557,7 +564,7 @@ class ProjectConfig(object):
return (self.is_desktop_mode() or
set(['build', 'build-libs', 'clean-build']).intersection(self.commands))
def needs_template(self):
def needs_builder(self):
return (self.is_build_cmd() or
set(['install', 'publish', 'review']).intersection(self.commands))
......@@ -589,14 +596,14 @@ class ProjectConfig(object):
def check_arch_restrictions(self):
if self.is_arch_agnostic():
if self.config["arch"] != "all":
raise ClickableException('The "{}" build template needs architecture "all", but "{}" was specified'.format(
self.config['template'],
raise ClickableException('The "{}" builder needs architecture "all", but "{}" was specified'.format(
self.config['builder'],
self.config['arch'],
))
if (self.config["restrict_arch"] and
self.config["restrict_arch"] != "all"):
raise ClickableException('The "{}" build template needs architecture "all", but "restrict_arch" was set to "{}"'.format(
self.config['template'],
raise ClickableException('The "{}" builder needs architecture "all", but "restrict_arch" was set to "{}"'.format(
self.config['builder'],
self.config['restrict_arch'],
))
else:
......@@ -622,20 +629,20 @@ class ProjectConfig(object):
if self.config['install_qml']:
logger.warning("Be aware that QML modules are going to be installed to {}, which is not part of 'QML2_IMPORT_PATH' at runtime.".format(self.config['app_qml_dir']))
def check_template_rules(self):
if not self.needs_template():
def check_builder_rules(self):
if not self.needs_builder():
return
if self.config['template'] == Constants.CUSTOM and not self.config['build']:
raise ClickableException('When using the "custom" template you must specify a "build" in the config')
if self.config['template'] == Constants.GO and not self.config['gopath']:
raise ClickableException('When using the "go" template you must specify a "gopath" in the config or use the '
if self.config['builder'] == Constants.CUSTOM and not self.config['build']:
raise ClickableException('When using the "custom" builder you must specify a "build" in the config')
if self.config['builder'] == Constants.GO and not self.config['gopath']:
raise ClickableException('When using the "go" builder you must specify a "gopath" in the config or use the '
'"GOPATH" env variable')
if self.config['template'] == Constants.RUST and not self.config['cargo_home']:
raise ClickableException('When using the "rust" template you must specify a "cargo_home" in the config')
if self.config['builder'] == Constants.RUST and not self.config['cargo_home']:
raise ClickableException('When using the "rust" builder you must specify a "cargo_home" in the config')
if self.config['template'] and self.config['template'] not in Constants.templates:
raise ClickableException('"{}" is not a valid template ({})'.format(self.config['template'], ', '.join(Constants.templates)))
if self.config['builder'] and self.config['builder'] not in Constants.builders:
raise ClickableException('"{}" is not a valid builder ({})'.format(self.config['builder'], ', '.join(Constants.builders)))
def check_docker_configs(self):
if self.is_custom_docker_image:
......@@ -664,36 +671,36 @@ class ProjectConfig(object):
def check_config_errors(self):
self.check_clickable_version()
self.check_arch_restrictions()
self.check_template_rules()
self.check_builder_rules()
self.check_docker_configs()
self.check_desktop_configs()
def set_template_interactive(self):
if self.config['template'] or not self.needs_template():
def set_builder_interactive(self):
if self.config['builder'] or not self.needs_builder():
return
choice = input(
Colors.INFO + 'No build template was specified, would you like to auto detect the template [y/N]: ' + Colors.CLEAR
Colors.INFO + 'No builder was specified, would you like to auto detect the builder [y/N]: ' + Colors.CLEAR
).strip().lower()
if choice != 'y' and choice != 'yes':
raise ClickableException('Not auto detecting build template')
raise ClickableException('Not auto detecting builder')
template = None
builder = None
directory = os.listdir(os.getcwd())
if 'config.xml' in directory:
template = Constants.CORDOVA
builder = Constants.CORDOVA
if not template and 'CMakeLists.txt' in directory:
template = Constants.CMAKE
if not builder and 'CMakeLists.txt' in directory:
builder = Constants.CMAKE
pro_files = [f for f in directory if f.endswith('.pro')]
if pro_files:
template = Constants.QMAKE
builder = Constants.QMAKE
if not template:
template = Constants.PURE
if not builder:
builder = Constants.PURE
self.config['template'] = template
self.config['builder'] = builder
logger.info('Auto detected template to be "{}"'.format(template))
logger.info('Auto detected builder to be "{}"'.format(builder))
......@@ -201,7 +201,7 @@ class Container(object):
rust_config = ''
if self.config.template == Constants.RUST and self.config.cargo_home:
if self.config.builder == Constants.RUST and self.config.cargo_home:
logger.info("Caching cargo related files in {}".format(self.config.cargo_home))
cargo_registry = os.path.join(self.config.cargo_home, 'registry')
cargo_git = os.path.join(self.config.cargo_home, 'git')
......
......@@ -10,7 +10,7 @@ import inspect
from os.path import dirname, basename, isfile, join
import multiprocessing
from clickable.build_templates.base import Builder
from clickable.builders.base import Builder
from clickable.logger import logger
from clickable.exceptions import FileNotFoundException, ClickableException
......@@ -124,12 +124,12 @@ def env(name):
def get_builders():
builder_classes = {}
builder_dir = join(dirname(__file__), 'build_templates')
builder_dir = join(dirname(__file__), 'builders')
modules = glob.glob(join(builder_dir, '*.py'))
builder_modules = [basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
for name in builder_modules:
builder_submodule = __import__('clickable.build_templates.{}'.format(name), globals(), locals(), [name])
builder_submodule = __import__('clickable.builders.{}'.format(name), globals(), locals(), [name])
for name, cls in inspect.getmembers(builder_submodule):
if inspect.isclass(cls) and issubclass(cls, Builder) and cls.name:
builder_classes[cls.name] = cls
......
.. _build-templates:
.. _builders:
Build Templates
===============
Builders
========
Builders have been called Build Templates in the early days of Clickable.
pure-qml-qmake
--------------
......@@ -41,9 +42,11 @@ A project that does not need to be compiled. All files in the project root will
precompiled
-----------
A project that does not need to be compiled. All files in the project root will be copied into the click.
There may be precompiled binaries or libraries included in apps build with this template.
Specifying the :ref:`restrict_arch <clickable-json-restrict_arch>` in the clickable.json file can be useful with this template.
A project that does not need to be compiled. All files in the project root will
be copied into the click. There may be precompiled binaries or libraries
included in apps build with this builder. Specifying the
:ref:`restrict_arch <clickable-json-restrict_arch>` in the clickable.json file
can be useful with this builder.
python
------
......
......@@ -8,7 +8,7 @@ Example:
.. code-block:: javascript