Skip to content

Commit

Permalink
Tie templates to bases
Browse files Browse the repository at this point in the history
Signed-off-by: Kyle Fazzari <kyrofa@ubuntu.com>
  • Loading branch information
kyrofa committed Aug 10, 2018
1 parent aee3aea commit 21dd614
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 21 deletions.
18 changes: 12 additions & 6 deletions snapcraft/cli/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import os
import yaml
import sys
import tabulate

from ._options import get_project
from snapcraft.internal import common, project_loader
Expand All @@ -37,12 +38,17 @@ def templates(**kwargs):
click.echo("No templates supported")
return

# Wrap the output depending on terminal size
width = common.get_terminal_width()
for line in common.format_output_in_columns(
sorted(template_names), max_width=width
):
click.echo(line)
templates = []
for template_name in sorted(template_names):
template = project_loader.load_template(template_name)
templates.append(
{
"Template name": template_name,
"Supported bases": ", ".join(sorted(template.keys())),
}
)

click.echo(tabulate.tabulate(templates, headers="keys"))


@templatecli.command()
Expand Down
2 changes: 1 addition & 1 deletion snapcraft/internal/project_loader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
)
from ._schema import Validator # noqa: F401
from ._parts_config import PartsConfig # noqa: F401
from ._templates import apply_templates, template_yaml_path # noqa: F401
from ._templates import apply_templates, load_template, template_yaml_path # noqa: F401

if TYPE_CHECKING:
from snapcraft.project import Project # noqa: F401
Expand Down
39 changes: 28 additions & 11 deletions snapcraft/internal/project_loader/_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ def apply_templates(yaml_data: Dict[str, Any]) -> Dict[str, Any]:
# Don't modify the dict passed in
yaml_data = copy.deepcopy(yaml_data)

# Get the base being used for this project (defaults to "core16")
base = yaml_data.get("base", "core16")
# Get the base being used for this project. This is required in order to use
# templates, so raise an error if not specified.
base = yaml_data.get("base")
if not base:
raise errors.TemplateBaseRequiredError()

applied_template_names = set() # type: Set[str]
global_template_names = yaml_data.get("templates", [])
Expand Down Expand Up @@ -92,6 +95,12 @@ def apply_templates(yaml_data: Dict[str, Any]) -> Dict[str, Any]:


def template_yaml_path(template_name: str) -> str:
"""Return the file path to the template's template.yaml
:param str template_name: The name of the template
:returns: File path to the template.yaml
:raises: errors.TemplateNotFoundError if the template is not found
"""
template_yaml_path = os.path.join(
common.get_templatesdir(), template_name, "template.yaml"
)
Expand All @@ -102,27 +111,35 @@ def template_yaml_path(template_name: str) -> str:
return template_yaml_path


# Don't load the same template multiple times
@functools.lru_cache()
def _find_template(base: str, template_name: str) -> Dict[str, Any]:
with open(template_yaml_path(template_name), "r") as f:
full_template = yaml.safe_load(f)
def load_template(template_name: str) -> Dict[str, Any]:
"""Load and return the template with the given name.
:param str template_name: The name of the template to load
:raises: errors.TemplateNotFoundError if the template is not found
"""
return copy.deepcopy(__template_loader(template_name))


def _find_template(base: str, template_name: str) -> Dict[str, Any]:
try:
return full_template[base]
return load_template(template_name)[base]
except KeyError:
raise errors.TemplateUnsupportedBaseError(template_name, base)


# Don't load the same template multiple times
@functools.lru_cache()
def __template_loader(template_name: str) -> Dict[str, Any]:
with open(template_yaml_path(template_name), "r") as f:
return yaml.safe_load(f)


def _apply_template(
yaml_data: Dict[str, Any],
app_name: str,
template_name: str,
template_data: Dict[str, Any],
):
# Don't modify the template data
template_data = copy.deepcopy(template_data)

# Apply the app-specific components of the template (if any)
template_app_components = template_data.pop("apps", None)
if template_app_components:
Expand Down
4 changes: 4 additions & 0 deletions snapcraft/internal/project_loader/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ def __init__(self, message):
super().__init__(message=message)


class TemplateBaseRequiredError(ProjectLoaderError):
fmt = "Templates can only be used if the snapcraft.yaml specifies a 'base'"


class TemplateNotFoundError(ProjectLoaderError):
fmt = (
"Failed to find template {template_name!r}: "
Expand Down
39 changes: 36 additions & 3 deletions tests/unit/commands/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def setUp(self):
f.write(
textwrap.dedent(
"""\
core:
core16:
apps:
'*':
environment:
Expand All @@ -56,11 +56,42 @@ def setUp(self):
).format(template_name)
)

template_dir = os.path.join(templates_dir, "template3")
os.mkdir(template_dir)
with open(os.path.join(template_dir, "template.yaml"), "w") as f:
f.write(
textwrap.dedent(
"""\
core16:
parts:
template-part:
plugin: nil
core18:
parts:
template-part:
plugin: nil
"""
).format(template_name)
)

def test_list_templates(self):
result = self.run_command(["templates"])

self.assertThat(result.exit_code, Equals(0))
self.assertThat(result.output, Equals("template1 template2\n"))
self.assertThat(
result.output,
Equals(
textwrap.dedent(
"""\
Template name Supported bases
--------------- -----------------
template1 core16
template2 core16
template3 core16, core18
"""
)
),
)

def test_template(self):
result = self.run_command(["template", "template1"])
Expand All @@ -71,7 +102,7 @@ def test_template(self):
Equals(
textwrap.dedent(
"""\
core:
core16:
apps:
'*':
environment:
Expand All @@ -97,6 +128,7 @@ def test_expand_templates(self):
version: '1'
summary: test
description: test
base: core16
grade: stable
confinement: strict
Expand Down Expand Up @@ -124,6 +156,7 @@ def test_expand_templates(self):
version: '1'
summary: test
description: test
base: core16
grade: stable
confinement: strict
apps:
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/project_loader/test_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class BasicTemplateTest(TemplateTestBase):
version: "1"
summary: test
description: test
base: core16
grade: stable
confinement: strict
Expand All @@ -133,6 +134,7 @@ class BasicTemplateTest(TemplateTestBase):
version: "1"
summary: test
description: test
base: core16
grade: stable
confinement: strict
Expand Down Expand Up @@ -231,6 +233,7 @@ def test_template_merge(self):
version: "1"
summary: test
description: test
base: core16
grade: stable
confinement: strict
Expand Down Expand Up @@ -268,6 +271,7 @@ def test_invalid_root_template_format(self):
version: "1"
summary: test
description: test
base: core16
grade: stable
confinement: strict
Expand Down Expand Up @@ -302,6 +306,7 @@ def test_invalid_app_template_format(self):
version: "1"
summary: test
description: test
base: core16
grade: stable
confinement: strict
Expand Down Expand Up @@ -335,6 +340,7 @@ def test_invalid_template_is_validated(self):
version: "1"
summary: test
description: test
base: core16
grade: stable
confinement: strict
Expand Down Expand Up @@ -362,6 +368,7 @@ def test_non_existing_template(self):
version: "1"
summary: test
description: test
base: core16
grade: stable
confinement: strict
Expand All @@ -387,6 +394,7 @@ def test_conflicting_part(self):
version: "1"
summary: test
description: test
base: core16
grade: stable
confinement: strict
Expand All @@ -405,6 +413,31 @@ def test_conflicting_part(self):
self.assertThat(raised.template_name, Equals("test-template"))
self.assertThat(raised.part_name, Equals("template-part"))

def test_no_base(self):
raised = self.assertRaises(
errors.TemplateBaseRequiredError,
self.make_snapcraft_project,
textwrap.dedent(
"""\
name: test
version: "1"
summary: test
description: test
grade: stable
confinement: strict
apps:
test-app:
command: echo "hello"
templates: [test-template]
parts:
template-part:
plugin: nil
"""
),
)

def test_unsupported_base(self):
raised = self.assertRaises(
errors.TemplateUnsupportedBaseError,
Expand Down

0 comments on commit 21dd614

Please sign in to comment.