Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion rockcraft/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
import re
import typing
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Literal
from typing import TYPE_CHECKING, Annotated, Any, Literal

import craft_cli
import pydantic
import spdx_lookup # type: ignore[import-untyped]
from craft_application.models import (
Platform,
get_validator_by_regex,
)
from craft_application.models import Project as BaseProject
from craft_application.models.base import alias_generator
Expand Down Expand Up @@ -76,9 +77,44 @@
]


# Rock names are minimum 2 characters, stricter than standard projects.
# https://git.launchpad.net/review-tools/tree/reviewtools/schemas/rock.json
MESSAGE_INVALID_NAME = (
"invalid name: Names can only use ASCII lowercase letters, numbers, and hyphens. "
"They must have at least one letter, may not start or end with a hyphen, "
"and may not have two hyphens in a row. It must be 2-40 characters long."
)
_PROJECT_NAME_DESCRIPTION = """\
The name of the project. This is used when uploading, publishing, or installing.

The project name must consist only of lower-case ASCII letters (``a``-``z``), numerals
(``0``-``9``), and hyphens (``-``). It must contain at least one letter, not start or
end with a hyphen, and not contain two consecutive hyphens. The maximum length is 40
characters.
"""
ROCK_NAME_REGEX = r"^([a-z0-9][a-z0-9-]?)*[a-z]+([a-z0-9-]?[a-z0-9])+$"
ROCK_NAME_COMPILED_REGEX = re.compile(ROCK_NAME_REGEX)
RockName = Annotated[
str,
pydantic.BeforeValidator(
get_validator_by_regex(ROCK_NAME_COMPILED_REGEX, MESSAGE_INVALID_NAME)
),
pydantic.Field(
min_length=2,
max_length=40,
strict=True,
pattern=ROCK_NAME_REGEX,
description=_PROJECT_NAME_DESCRIPTION,
title="Rock Name",
examples=["go", "jq", "postgresql"],
),
]


class Project(BaseProject):
"""Rockcraft project definition."""

name: RockName
# Type of summary is Optional[str] in BaseProject
summary: str # type: ignore[reportIncompatibleVariableOverride]
description: str # type: ignore[reportIncompatibleVariableOverride]
Expand Down
13 changes: 5 additions & 8 deletions schema/rockcraft.json
Original file line number Diff line number Diff line change
Expand Up @@ -10697,16 +10697,13 @@
"name": {
"description": "The name of the project. This is used when uploading, publishing, or installing.\n\nThe project name must consist only of lower-case ASCII letters (``a``-``z``), numerals\n(``0``-``9``), and hyphens (``-``). It must contain at least one letter, not start or\nend with a hyphen, and not contain two consecutive hyphens. The maximum length is 40\ncharacters.\n",
"examples": [
"ubuntu",
"jupyterlab-desktop",
"lxd",
"digikam",
"kafka",
"mysql-router-k8s"
"go",
"jq",
"postgresql"
],
"maxLength": 40,
"minLength": 1,
"title": "Project Name",
"minLength": 2,
"title": "Rock Name",
"type": "string"
},
"title": {
Expand Down
12 changes: 5 additions & 7 deletions tests/unit/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import datetime
import os
import re
import subprocess
from pathlib import Path
from typing import cast
Expand All @@ -24,10 +25,9 @@
import pytest
import yaml
from craft_application.errors import CraftValidationError
from craft_application.models.constraints import MESSAGE_INVALID_NAME
from craft_providers.bases import ubuntu
from rockcraft.models import Project
from rockcraft.models.project import Platform
from rockcraft.models.project import MESSAGE_INVALID_NAME, Platform
from rockcraft.pebble import Service
from rockcraft.services.project import RockcraftProjectService

Expand Down Expand Up @@ -275,10 +275,8 @@ def test_project_title_empty_invalid_name(yaml_loaded_data):
with pytest.raises(CraftValidationError) as err:
load_project_yaml(yaml_loaded_data)

expected = (
"Bad rockcraft.yaml content:\n"
"- invalid name: Names can only use ASCII lowercase letters, numbers, and hyphens. They must have at least one letter, may not start or end with a hyphen, and may not have two hyphens in a row. "
r"\(in field 'name'\)"
expected = re.escape(
f"Bad rockcraft.yaml content:\n- {MESSAGE_INVALID_NAME} (in field 'name')"
)
assert err.match(expected)

Expand Down Expand Up @@ -515,7 +513,6 @@ def reload_project_platforms(new_platforms=None):
"aaa",
"a00",
"0aaa",
"a",
"a-00",
"a-a-a",
"a-000-bbb",
Expand All @@ -533,6 +530,7 @@ def test_project_name_valid(yaml_loaded_data, valid_name):
("invalid_name", "expected_message"),
[
("", MESSAGE_INVALID_NAME),
("a", MESSAGE_INVALID_NAME),
("AAA", MESSAGE_INVALID_NAME),
("a--a", MESSAGE_INVALID_NAME),
("aa-", MESSAGE_INVALID_NAME),
Expand Down
Loading