mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 04:15:11 +00:00
Allow Serena home directory to be overridden with SERENA_HOME env var,
defining the relevant path variables in SerenaPaths
This commit is contained in:
1 parent
904a25053e
commit
7b287fa602
7 files changed
+64
-49
No files matched your search
+15
-18
@@ -21,14 +21,10 @@ from serena.config.serena_config import ProjectConfig, SerenaConfig, SerenaPaths
|
||||
from serena.constants import (
|
||||
DEFAULT_CONTEXT,
|
||||
DEFAULT_MODES,
|
||||
PROMPT_TEMPLATES_DIR_IN_USER_HOME,
|
||||
PROMPT_TEMPLATES_DIR_INTERNAL,
|
||||
SERENA_LOG_FORMAT,
|
||||
SERENA_MANAGED_DIR_IN_HOME,
|
||||
SERENAS_OWN_CONTEXT_YAMLS_DIR,
|
||||
SERENAS_OWN_MODE_YAMLS_DIR,
|
||||
USER_CONTEXT_YAMLS_DIR,
|
||||
USER_MODE_YAMLS_DIR,
|
||||
)
|
||||
from serena.mcp import SerenaMCPFactory, SerenaMCPFactorySingleProcess
|
||||
from serena.project import Project
|
||||
@@ -270,7 +266,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
if not (name or from_internal):
|
||||
raise click.UsageError("Provide at least one of --name or --from-internal.")
|
||||
mode_name = name or from_internal
|
||||
dest = os.path.join(USER_MODE_YAMLS_DIR, f"{mode_name}.yml")
|
||||
dest = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
|
||||
src = (
|
||||
os.path.join(SERENAS_OWN_MODE_YAMLS_DIR, f"{from_internal}.yml")
|
||||
if from_internal
|
||||
@@ -289,7 +285,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
@click.command("edit", help="Edit a custom mode YAML file.")
|
||||
@click.argument("mode_name")
|
||||
def edit(mode_name: str) -> None:
|
||||
path = os.path.join(USER_MODE_YAMLS_DIR, f"{mode_name}.yml")
|
||||
path = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
|
||||
if not os.path.exists(path):
|
||||
if mode_name in SerenaAgentMode.list_registered_mode_names(include_user_modes=False):
|
||||
click.echo(
|
||||
@@ -305,7 +301,7 @@ class ModeCommands(AutoRegisteringGroup):
|
||||
@click.command("delete", help="Delete a custom mode file.")
|
||||
@click.argument("mode_name")
|
||||
def delete(mode_name: str) -> None:
|
||||
path = os.path.join(USER_MODE_YAMLS_DIR, f"{mode_name}.yml")
|
||||
path = os.path.join(SerenaPaths().user_modes_dir, f"{mode_name}.yml")
|
||||
if not os.path.exists(path):
|
||||
click.echo(f"Custom mode '{mode_name}' not found.")
|
||||
return
|
||||
@@ -347,7 +343,7 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
if not (name or from_internal):
|
||||
raise click.UsageError("Provide at least one of --name or --from-internal.")
|
||||
ctx_name = name or from_internal
|
||||
dest = os.path.join(USER_CONTEXT_YAMLS_DIR, f"{ctx_name}.yml")
|
||||
dest = os.path.join(SerenaPaths().user_contexts_dir, f"{ctx_name}.yml")
|
||||
src = (
|
||||
os.path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, f"{from_internal}.yml")
|
||||
if from_internal
|
||||
@@ -366,7 +362,7 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
@click.command("edit", help="Edit a custom context YAML file.")
|
||||
@click.argument("context_name")
|
||||
def edit(context_name: str) -> None:
|
||||
path = os.path.join(USER_CONTEXT_YAMLS_DIR, f"{context_name}.yml")
|
||||
path = os.path.join(SerenaPaths().user_contexts_dir, f"{context_name}.yml")
|
||||
if not os.path.exists(path):
|
||||
if context_name in SerenaAgentContext.list_registered_context_names(include_user_contexts=False):
|
||||
click.echo(
|
||||
@@ -382,7 +378,7 @@ class ContextCommands(AutoRegisteringGroup):
|
||||
@click.command("delete", help="Delete a custom context file.")
|
||||
@click.argument("context_name")
|
||||
def delete(context_name: str) -> None:
|
||||
path = os.path.join(USER_CONTEXT_YAMLS_DIR, f"{context_name}.yml")
|
||||
path = os.path.join(SerenaPaths().user_contexts_dir, f"{context_name}.yml")
|
||||
if not os.path.exists(path):
|
||||
click.echo(f"Custom context '{context_name}' not found.")
|
||||
return
|
||||
@@ -401,10 +397,9 @@ class SerenaConfigCommands(AutoRegisteringGroup):
|
||||
"edit", help="Edit serena_config.yml in your default editor. Will create a config file from the template if no config is found."
|
||||
)
|
||||
def edit() -> None:
|
||||
config_path = os.path.join(SERENA_MANAGED_DIR_IN_HOME, "serena_config.yml")
|
||||
if not os.path.exists(config_path):
|
||||
SerenaConfig.generate_config_file(config_path)
|
||||
_open_in_editor(config_path)
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
assert serena_config.config_file_path is not None
|
||||
_open_in_editor(serena_config.config_file_path)
|
||||
|
||||
|
||||
class ProjectCommands(AutoRegisteringGroup):
|
||||
@@ -811,8 +806,9 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
|
||||
@staticmethod
|
||||
def _get_user_prompt_yaml_path(prompt_yaml_name: str) -> str:
|
||||
os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
|
||||
return os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, prompt_yaml_name)
|
||||
templates_dir = SerenaPaths().user_prompt_templates_dir
|
||||
os.makedirs(templates_dir, exist_ok=True)
|
||||
return os.path.join(templates_dir, prompt_yaml_name)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list", help="Lists yamls that are used for defining prompts.")
|
||||
@@ -863,9 +859,10 @@ class PromptCommands(AutoRegisteringGroup):
|
||||
@staticmethod
|
||||
@click.command("list-overrides", help="List existing prompt override files")
|
||||
def list_overrides() -> None:
|
||||
os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
|
||||
user_templates_dir = SerenaPaths().user_prompt_templates_dir
|
||||
os.makedirs(user_templates_dir, exist_ok=True)
|
||||
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
|
||||
override_files = glob.glob(os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, "*.yml"))
|
||||
override_files = glob.glob(os.path.join(user_templates_dir, "*.yml"))
|
||||
for file_path in override_files:
|
||||
if os.path.basename(file_path) in serena_prompt_yaml_names:
|
||||
click.echo(file_path)
|
||||
|
||||
@@ -11,7 +11,7 @@ import yaml
|
||||
from sensai.util import logging
|
||||
from sensai.util.string import ToStringMixin
|
||||
|
||||
from serena.config.serena_config import ToolInclusionDefinition
|
||||
from serena.config.serena_config import SerenaPaths, ToolInclusionDefinition
|
||||
from serena.constants import (
|
||||
DEFAULT_CONTEXT,
|
||||
DEFAULT_MODES,
|
||||
@@ -19,8 +19,6 @@ from serena.constants import (
|
||||
SERENA_FILE_ENCODING,
|
||||
SERENAS_OWN_CONTEXT_YAMLS_DIR,
|
||||
SERENAS_OWN_MODE_YAMLS_DIR,
|
||||
USER_CONTEXT_YAMLS_DIR,
|
||||
USER_MODE_YAMLS_DIR,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -65,14 +63,14 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
|
||||
def get_path(cls, name: str) -> str:
|
||||
"""Get the path to the YAML file for a mode."""
|
||||
fname = f"{name}.yml"
|
||||
custom_mode_path = os.path.join(USER_MODE_YAMLS_DIR, fname)
|
||||
custom_mode_path = os.path.join(SerenaPaths().user_modes_dir, fname)
|
||||
if os.path.exists(custom_mode_path):
|
||||
return custom_mode_path
|
||||
|
||||
own_yaml_path = os.path.join(SERENAS_OWN_MODE_YAMLS_DIR, fname)
|
||||
if not os.path.exists(own_yaml_path):
|
||||
raise FileNotFoundError(
|
||||
f"Mode {name} not found in {USER_MODE_YAMLS_DIR} or in {SERENAS_OWN_MODE_YAMLS_DIR}."
|
||||
f"Mode {name} not found in {SerenaPaths().user_modes_dir} or in {SERENAS_OWN_MODE_YAMLS_DIR}."
|
||||
f"Available modes:\n{cls.list_registered_mode_names()}"
|
||||
)
|
||||
return own_yaml_path
|
||||
@@ -102,7 +100,7 @@ class SerenaAgentMode(ToolInclusionDefinition, ToStringMixin):
|
||||
@classmethod
|
||||
def list_custom_mode_names(cls) -> list[str]:
|
||||
"""Names of all custom modes defined by the user."""
|
||||
return [f.stem for f in Path(USER_MODE_YAMLS_DIR).glob("*.yml")]
|
||||
return [f.stem for f in Path(SerenaPaths().user_modes_dir).glob("*.yml")]
|
||||
|
||||
@classmethod
|
||||
def load_default_modes(cls) -> list[Self]:
|
||||
@@ -166,14 +164,14 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
|
||||
def get_path(cls, name: str) -> str:
|
||||
"""Get the path to the YAML file for a context."""
|
||||
fname = f"{name}.yml"
|
||||
custom_context_path = os.path.join(USER_CONTEXT_YAMLS_DIR, fname)
|
||||
custom_context_path = os.path.join(SerenaPaths().user_contexts_dir, fname)
|
||||
if os.path.exists(custom_context_path):
|
||||
return custom_context_path
|
||||
|
||||
own_yaml_path = os.path.join(SERENAS_OWN_CONTEXT_YAMLS_DIR, fname)
|
||||
if not os.path.exists(own_yaml_path):
|
||||
raise FileNotFoundError(
|
||||
f"Context {name} not found in {USER_CONTEXT_YAMLS_DIR} or in {SERENAS_OWN_CONTEXT_YAMLS_DIR}."
|
||||
f"Context {name} not found in {SerenaPaths().user_contexts_dir} or in {SERENAS_OWN_CONTEXT_YAMLS_DIR}."
|
||||
f"Available contexts:\n{cls.list_registered_context_names()}"
|
||||
)
|
||||
return own_yaml_path
|
||||
@@ -211,7 +209,7 @@ class SerenaAgentContext(ToolInclusionDefinition, ToStringMixin):
|
||||
@classmethod
|
||||
def list_custom_context_names(cls) -> list[str]:
|
||||
"""Names of all custom contexts defined by the user."""
|
||||
return [f.stem for f in Path(USER_CONTEXT_YAMLS_DIR).glob("*.yml")]
|
||||
return [f.stem for f in Path(SerenaPaths().user_contexts_dir).glob("*.yml")]
|
||||
|
||||
@classmethod
|
||||
def load_default(cls) -> Self:
|
||||
|
||||
@@ -25,7 +25,6 @@ from serena.constants import (
|
||||
REPO_ROOT,
|
||||
SERENA_CONFIG_TEMPLATE_FILE,
|
||||
SERENA_FILE_ENCODING,
|
||||
SERENA_MANAGED_DIR_IN_HOME,
|
||||
SERENA_MANAGED_DIR_NAME,
|
||||
)
|
||||
from serena.util.general import load_yaml, save_yaml
|
||||
@@ -53,9 +52,32 @@ class SerenaPaths:
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.user_config_dir: str = SERENA_MANAGED_DIR_IN_HOME
|
||||
home_dir = os.getenv("SERENA_HOME")
|
||||
if home_dir is None or home_dir.strip() == "":
|
||||
home_dir = str(Path.home() / SERENA_MANAGED_DIR_NAME)
|
||||
else:
|
||||
home_dir = home_dir.strip()
|
||||
self.serena_user_home_dir: str = home_dir
|
||||
"""
|
||||
the path to the user's Serena configuration directory, which is typically ~/.serena
|
||||
the path to the Serena home directory, where the user's configuration/data is stored.
|
||||
This is ~/.serena by default, but it can be overridden via the SERENA_HOME environment variable.
|
||||
"""
|
||||
self.user_prompt_templates_dir: str = os.path.join(self.serena_user_home_dir, "prompt_templates")
|
||||
"""
|
||||
directory containing prompt templates defined by the user.
|
||||
Prompts defined by the user take precedence over Serena's built-in prompt templates.
|
||||
"""
|
||||
self.user_contexts_dir: str = os.path.join(self.serena_user_home_dir, "contexts")
|
||||
"""
|
||||
directory containing contexts defined by the user.
|
||||
If a name of a context matches a name of a context in SERENAS_OWN_CONTEXT_YAMLS_DIR,
|
||||
the user context will override the default context definition.
|
||||
"""
|
||||
self.user_modes_dir: str = os.path.join(self.serena_user_home_dir, "modes")
|
||||
"""
|
||||
directory containing modes defined by the user.
|
||||
If a name of a mode matches a name of a mode in SERENAS_OWN_MODES_YAML_DIR,
|
||||
the user mode will override the default mode definition.
|
||||
"""
|
||||
|
||||
def get_next_log_file_path(self, prefix: str) -> str:
|
||||
@@ -63,7 +85,7 @@ class SerenaPaths:
|
||||
:param prefix: the filename prefix indicating the type of the log file
|
||||
:return: the full path to the log file to use
|
||||
"""
|
||||
log_dir = os.path.join(self.user_config_dir, "logs", datetime.now().strftime("%Y-%m-%d"))
|
||||
log_dir = os.path.join(self.serena_user_home_dir, "logs", datetime.now().strftime("%Y-%m-%d"))
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
return os.path.join(log_dir, prefix + "_" + datetime_tag() + ".txt")
|
||||
|
||||
@@ -377,7 +399,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
return ["config_file_path"]
|
||||
|
||||
@classmethod
|
||||
def generate_config_file(cls, config_file_path: str) -> None:
|
||||
def _generate_config_file(cls, config_file_path: str) -> None:
|
||||
"""
|
||||
Generates a Serena configuration file at the specified path from the template file.
|
||||
|
||||
@@ -395,7 +417,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
if is_running_in_docker():
|
||||
return os.path.join(REPO_ROOT, cls.CONFIG_FILE_DOCKER)
|
||||
else:
|
||||
config_path = os.path.join(SERENA_MANAGED_DIR_IN_HOME, cls.CONFIG_FILE)
|
||||
config_path = os.path.join(SerenaPaths().serena_user_home_dir, cls.CONFIG_FILE)
|
||||
|
||||
# if the config file does not exist, check if we can migrate it from the old location
|
||||
if not os.path.exists(config_path):
|
||||
@@ -419,7 +441,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
if not generate_if_missing:
|
||||
raise FileNotFoundError(f"Serena configuration file not found: {config_file_path}")
|
||||
log.info(f"Serena configuration file not found at {config_file_path}, autogenerating...")
|
||||
cls.generate_config_file(config_file_path)
|
||||
cls._generate_config_file(config_file_path)
|
||||
|
||||
# load the configuration
|
||||
log.info(f"Loading Serena configuration from {config_file_path}")
|
||||
|
||||
@@ -4,22 +4,14 @@ _repo_root_path = Path(__file__).parent.parent.parent.resolve()
|
||||
_serena_pkg_path = Path(__file__).parent.resolve()
|
||||
|
||||
SERENA_MANAGED_DIR_NAME = ".serena"
|
||||
_serena_in_home_managed_dir = Path.home() / ".serena"
|
||||
|
||||
SERENA_MANAGED_DIR_IN_HOME = str(_serena_in_home_managed_dir)
|
||||
|
||||
# TODO: Path-related constants should be moved to SerenaPaths; don't add further constants here.
|
||||
REPO_ROOT = str(_repo_root_path)
|
||||
PROMPT_TEMPLATES_DIR_INTERNAL = str(_serena_pkg_path / "resources" / "config" / "prompt_templates")
|
||||
PROMPT_TEMPLATES_DIR_IN_USER_HOME = str(_serena_in_home_managed_dir / "prompt_templates")
|
||||
SERENAS_OWN_CONTEXT_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "contexts")
|
||||
"""The contexts that are shipped with the Serena package, i.e. the default contexts."""
|
||||
USER_CONTEXT_YAMLS_DIR = str(_serena_in_home_managed_dir / "contexts")
|
||||
"""Contexts defined by the user. If a name of a context matches a name of a context in SERENAS_OWN_CONTEXT_YAMLS_DIR, the user context will override the default one."""
|
||||
SERENAS_OWN_MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "modes")
|
||||
"""The modes that are shipped with the Serena package, i.e. the default modes."""
|
||||
USER_MODE_YAMLS_DIR = str(_serena_in_home_managed_dir / "modes")
|
||||
"""Modes defined by the user. If a name of a mode matches a name of a mode in SERENAS_OWN_MODE_YAMLS_DIR, the user mode will override the default one."""
|
||||
INTERNAL_MODE_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "internal_modes")
|
||||
"""Internal modes, never overridden by user modes."""
|
||||
SERENA_DASHBOARD_DIR = str(_serena_pkg_path / "resources" / "dashboard")
|
||||
|
||||
@@ -4,7 +4,8 @@ from collections.abc import Iterator
|
||||
|
||||
from sensai.util.logging import LogTime
|
||||
|
||||
from serena.constants import SERENA_MANAGED_DIR_IN_HOME, SERENA_MANAGED_DIR_NAME
|
||||
from serena.config.serena_config import SerenaPaths
|
||||
from serena.constants import SERENA_MANAGED_DIR_NAME
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
@@ -43,7 +44,7 @@ class LanguageServerFactory:
|
||||
self.project_root,
|
||||
timeout=self.ls_timeout,
|
||||
solidlsp_settings=SolidLSPSettings(
|
||||
solidlsp_dir=SERENA_MANAGED_DIR_IN_HOME,
|
||||
solidlsp_dir=SerenaPaths().serena_user_home_dir,
|
||||
project_data_relative_path=SERENA_MANAGED_DIR_NAME,
|
||||
ls_specific_settings=self.ls_specific_settings or {},
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR_IN_USER_HOME, PROMPT_TEMPLATES_DIR_INTERNAL
|
||||
from serena.config.serena_config import SerenaPaths
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR_INTERNAL
|
||||
from serena.generated.generated_prompt_factory import PromptFactory
|
||||
|
||||
|
||||
@@ -10,5 +11,6 @@ class SerenaPromptFactory(PromptFactory):
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
|
||||
super().__init__(prompts_dir=[PROMPT_TEMPLATES_DIR_IN_USER_HOME, PROMPT_TEMPLATES_DIR_INTERNAL])
|
||||
user_templates_dir = SerenaPaths().user_prompt_templates_dir
|
||||
os.makedirs(user_templates_dir, exist_ok=True)
|
||||
super().__init__(prompts_dir=[user_templates_dir, PROMPT_TEMPLATES_DIR_INTERNAL])
|
||||
+5
-2
@@ -7,7 +7,8 @@ import pytest
|
||||
from blib2to3.pgen2.parse import contextmanager
|
||||
from sensai.util.logging import configure
|
||||
|
||||
from serena.constants import SERENA_MANAGED_DIR_IN_HOME, SERENA_MANAGED_DIR_NAME
|
||||
from serena.config.serena_config import SerenaPaths
|
||||
from serena.constants import SERENA_MANAGED_DIR_NAME
|
||||
from serena.project import Project
|
||||
from serena.util.file_system import GitignoreParser
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
@@ -49,7 +50,9 @@ def _create_ls(
|
||||
return SolidLanguageServer.create(
|
||||
config,
|
||||
repo_path,
|
||||
solidlsp_settings=SolidLSPSettings(solidlsp_dir=SERENA_MANAGED_DIR_IN_HOME, project_data_relative_path=SERENA_MANAGED_DIR_NAME),
|
||||
solidlsp_settings=SolidLSPSettings(
|
||||
solidlsp_dir=SerenaPaths().serena_user_home_dir, project_data_relative_path=SERENA_MANAGED_DIR_NAME
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user