xmipp3-installer 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xmipp3_installer/__init__.py +1 -0
- xmipp3_installer/__main__.py +6 -0
- xmipp3_installer/api_client/api_client.py +50 -0
- xmipp3_installer/api_client/assembler/installation_info_assembler.py +181 -0
- xmipp3_installer/application/__init__.py +1 -0
- xmipp3_installer/application/cli/__init__.py +1 -0
- xmipp3_installer/application/cli/arguments/__init__.py +8 -0
- xmipp3_installer/application/cli/arguments/modes.py +130 -0
- xmipp3_installer/application/cli/arguments/params.py +76 -0
- xmipp3_installer/application/cli/cli.py +271 -0
- xmipp3_installer/application/cli/parsers/base_help_formatter.py +244 -0
- xmipp3_installer/application/cli/parsers/error_handler_parser.py +68 -0
- xmipp3_installer/application/cli/parsers/format.py +35 -0
- xmipp3_installer/application/cli/parsers/general_help_formatter.py +92 -0
- xmipp3_installer/application/cli/parsers/mode_help_formatter.py +115 -0
- xmipp3_installer/application/logger/__init__.py +0 -0
- xmipp3_installer/application/logger/errors.py +28 -0
- xmipp3_installer/application/logger/logger.py +230 -0
- xmipp3_installer/application/logger/predefined_messages.py +66 -0
- xmipp3_installer/application/user_interactions.py +16 -0
- xmipp3_installer/installer/__init__.py +1 -0
- xmipp3_installer/installer/constants/__init__.py +17 -0
- xmipp3_installer/installer/constants/paths.py +32 -0
- xmipp3_installer/installer/handlers/__init__.py +1 -0
- xmipp3_installer/installer/handlers/cmake/__init__.py +1 -0
- xmipp3_installer/installer/handlers/cmake/cmake_constants.py +27 -0
- xmipp3_installer/installer/handlers/cmake/cmake_handler.py +69 -0
- xmipp3_installer/installer/handlers/conda_handler.py +13 -0
- xmipp3_installer/installer/handlers/generic_package_handler.py +18 -0
- xmipp3_installer/installer/handlers/git_handler.py +185 -0
- xmipp3_installer/installer/handlers/shell_handler.py +114 -0
- xmipp3_installer/installer/handlers/versions_manager.py +98 -0
- xmipp3_installer/installer/installer_service.py +66 -0
- xmipp3_installer/installer/modes/__init__.py +1 -0
- xmipp3_installer/installer/modes/mode_all_executor.py +63 -0
- xmipp3_installer/installer/modes/mode_clean/__init__.py +1 -0
- xmipp3_installer/installer/modes/mode_clean/mode_clean_all_executor.py +44 -0
- xmipp3_installer/installer/modes/mode_clean/mode_clean_bin_executor.py +94 -0
- xmipp3_installer/installer/modes/mode_clean/mode_clean_executor.py +45 -0
- xmipp3_installer/installer/modes/mode_cmake/__init__.py +1 -0
- xmipp3_installer/installer/modes/mode_cmake/mode_cmake_executor.py +55 -0
- xmipp3_installer/installer/modes/mode_cmake/mode_compile_and_install_executor.py +49 -0
- xmipp3_installer/installer/modes/mode_cmake/mode_config_build_executor.py +64 -0
- xmipp3_installer/installer/modes/mode_config_executor.py +46 -0
- xmipp3_installer/installer/modes/mode_executor.py +43 -0
- xmipp3_installer/installer/modes/mode_get_sources_executor.py +132 -0
- xmipp3_installer/installer/modes/mode_git_executor.py +41 -0
- xmipp3_installer/installer/modes/mode_selector.py +25 -0
- xmipp3_installer/installer/modes/mode_sync/mode_add_model_executor.py +104 -0
- xmipp3_installer/installer/modes/mode_sync/mode_get_models_executor.py +51 -0
- xmipp3_installer/installer/modes/mode_sync/mode_sync_executor.py +48 -0
- xmipp3_installer/installer/modes/mode_sync/mode_test_executor.py +91 -0
- xmipp3_installer/installer/modes/mode_version_executor.py +164 -0
- xmipp3_installer/installer/orquestrator.py +37 -0
- xmipp3_installer/installer/urls.py +8 -0
- xmipp3_installer/repository/__init__.py +1 -0
- xmipp3_installer/repository/config.py +241 -0
- xmipp3_installer/repository/config_vars/__init__.py +0 -0
- xmipp3_installer/repository/config_vars/config_values_adapter.py +107 -0
- xmipp3_installer/repository/config_vars/default_values.py +36 -0
- xmipp3_installer/repository/config_vars/variables.py +48 -0
- xmipp3_installer/repository/invalid_config_line.py +15 -0
- xmipp3_installer/shared/file_operations.py +18 -0
- xmipp3_installer/shared/singleton.py +25 -0
- xmipp3_installer-1.0.0.dist-info/LICENSE +674 -0
- xmipp3_installer-1.0.0.dist-info/METADATA +729 -0
- xmipp3_installer-1.0.0.dist-info/RECORD +70 -0
- xmipp3_installer-1.0.0.dist-info/WHEEL +5 -0
- xmipp3_installer-1.0.0.dist-info/entry_points.txt +2 -0
- xmipp3_installer-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""### Functions that interact with CMake."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
|
|
7
|
+
from xmipp3_installer.installer.handlers.cmake import cmake_constants
|
|
8
|
+
from xmipp3_installer.repository.config_vars import variables
|
|
9
|
+
|
|
10
|
+
def get_cmake_path(config: Dict[str, Any]) -> str:
|
|
11
|
+
"""
|
|
12
|
+
### Retrieves information about the CMake package and updates the dictionary accordingly.
|
|
13
|
+
|
|
14
|
+
#### Params:
|
|
15
|
+
- packages (dict): Dictionary containing package information.
|
|
16
|
+
|
|
17
|
+
#### Returns:
|
|
18
|
+
- (dict): Param 'packages' with the 'CMAKE' key updated based on the availability of 'cmake'.
|
|
19
|
+
"""
|
|
20
|
+
return config.get(variables.CMAKE) or shutil.which(cmake_constants.DEFAULT_CMAKE)
|
|
21
|
+
|
|
22
|
+
def get_cmake_vars_str(config: Dict[str, Any]) -> str:
|
|
23
|
+
"""
|
|
24
|
+
### Converts the variables in the config dictionary into a list as CMake args.
|
|
25
|
+
|
|
26
|
+
#### Params:
|
|
27
|
+
- config (dict): Dictionary to obtain the parameters from.
|
|
28
|
+
"""
|
|
29
|
+
result = []
|
|
30
|
+
for (key, value) in config.items():
|
|
31
|
+
if key not in variables.INTERNAL_LOGIC_VARS and bool(value):
|
|
32
|
+
result.append(f"-D{key}={value}")
|
|
33
|
+
return ' '.join(result)
|
|
34
|
+
|
|
35
|
+
def get_library_versions_from_cmake_file(path: str) -> Dict[str, Any]:
|
|
36
|
+
"""
|
|
37
|
+
### Obtains the library versions from the CMake cache file.
|
|
38
|
+
|
|
39
|
+
#### Params:
|
|
40
|
+
- path (str): Path to the file containing all versions.
|
|
41
|
+
|
|
42
|
+
#### Returns:
|
|
43
|
+
- (dict(str, any)): Dictionary containing all the library versions in the file.
|
|
44
|
+
"""
|
|
45
|
+
if not os.path.exists(path):
|
|
46
|
+
return {}
|
|
47
|
+
|
|
48
|
+
result = {}
|
|
49
|
+
with open(path, 'r') as versions_file:
|
|
50
|
+
for line in versions_file.readlines():
|
|
51
|
+
result.update(__get_library_version_from_line(line))
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
def __get_library_version_from_line(version_line: str) -> Dict[str, Any]:
|
|
55
|
+
"""
|
|
56
|
+
### Retrieves the name and version of the library in the given line.
|
|
57
|
+
|
|
58
|
+
#### Params:
|
|
59
|
+
- version_line (str): Text line containing the name and version of the library.
|
|
60
|
+
|
|
61
|
+
#### Returns:
|
|
62
|
+
- (dict(str, any)): Dictionary where the key is the name and the value is the version.
|
|
63
|
+
"""
|
|
64
|
+
library_with_version = {}
|
|
65
|
+
name_and_version = version_line.replace("\n", "").split('=')
|
|
66
|
+
if len(name_and_version) == 2:
|
|
67
|
+
version = name_and_version[1] if name_and_version[1] else None
|
|
68
|
+
library_with_version[name_and_version[0]] = version
|
|
69
|
+
return library_with_version
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""### Functions that interact with Conda via shell."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
def get_conda_prefix_path() -> Optional[str]:
|
|
7
|
+
"""
|
|
8
|
+
### Returns the path for the current Conda enviroment.
|
|
9
|
+
|
|
10
|
+
#### Returns:
|
|
11
|
+
- (str | None): Path for current Conda enviroment.
|
|
12
|
+
"""
|
|
13
|
+
return os.environ.get('CONDA_PREFIX')
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""### Contains functions that can interact with packages via shell with a generic interface."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from xmipp3_installer.installer.handlers import shell_handler
|
|
6
|
+
|
|
7
|
+
def get_package_version(package_name: str) -> Optional[str]:
|
|
8
|
+
"""
|
|
9
|
+
### Retrieves the version of a package or program by executing '[package_name] --version' command.
|
|
10
|
+
|
|
11
|
+
Params:
|
|
12
|
+
- package_name (str): Name of the package or program.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
- (str | None): Version information of the package or None if not found or errors happened.
|
|
16
|
+
"""
|
|
17
|
+
ret_code, output = shell_handler.run_shell_command(f'{package_name} --version')
|
|
18
|
+
return output if ret_code == 0 else None
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""### Functions that interact with Git via shell."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from xmipp3_installer.application.logger.logger import logger
|
|
7
|
+
from xmipp3_installer.installer.constants import paths
|
|
8
|
+
from xmipp3_installer.installer.handlers import shell_handler
|
|
9
|
+
|
|
10
|
+
def get_current_branch(dir: str='./') -> str:
|
|
11
|
+
"""
|
|
12
|
+
### Returns the current branch of the repository of the given directory or empty string if it is not a repository or a recognizable tag.
|
|
13
|
+
|
|
14
|
+
#### Params:
|
|
15
|
+
- dir (str): Optional. Directory of the repository to get current branch from. Default is current directory.
|
|
16
|
+
|
|
17
|
+
#### Returns:
|
|
18
|
+
- (str): The name of the branch, 'HEAD' if a tag, or empty string if given directory is not a repository or a recognizable tag.
|
|
19
|
+
"""
|
|
20
|
+
ret_code, branch_name = shell_handler.run_shell_command("git rev-parse --abbrev-ref HEAD", cwd=dir)
|
|
21
|
+
# If there was an error, we are in no branch
|
|
22
|
+
return branch_name if not ret_code else ''
|
|
23
|
+
|
|
24
|
+
def is_tag(dir: str='./') -> bool:
|
|
25
|
+
"""
|
|
26
|
+
### Returns True if the current Xmipp repository is in a tag.
|
|
27
|
+
|
|
28
|
+
#### Params:
|
|
29
|
+
- dir (str): Optional. Directory of the repository where the check will happen. Default is current directory.
|
|
30
|
+
|
|
31
|
+
#### Returns:
|
|
32
|
+
- (bool): True if the repository is a tag. False otherwise.
|
|
33
|
+
"""
|
|
34
|
+
current_branch = get_current_branch(dir=dir)
|
|
35
|
+
return not current_branch or current_branch == "HEAD"
|
|
36
|
+
|
|
37
|
+
def is_branch_up_to_date(dir: str='./') -> bool:
|
|
38
|
+
"""
|
|
39
|
+
### Returns True if the current branch is up to date, or False otherwise or if some error happened.
|
|
40
|
+
|
|
41
|
+
#### Params:
|
|
42
|
+
- dir (str): Optional. Directory of the repository to get current branch from. Default is current directory.
|
|
43
|
+
|
|
44
|
+
#### Returns:
|
|
45
|
+
- (bool): True if the current branch is up to date, or False otherwise or if some error happened.
|
|
46
|
+
"""
|
|
47
|
+
current_branch = get_current_branch(dir=dir)
|
|
48
|
+
if not current_branch:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
ret_code = shell_handler.run_shell_command("git fetch", cwd=dir)[0]
|
|
52
|
+
if ret_code != 0:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
latest_local_commit = shell_handler.run_shell_command(f"git rev-parse {current_branch}", cwd=dir)[1]
|
|
56
|
+
ret_code, latest_remote_commit = shell_handler.run_shell_command(f"git rev-parse origin/{current_branch}")
|
|
57
|
+
if ret_code != 0:
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
return latest_local_commit == latest_remote_commit
|
|
61
|
+
|
|
62
|
+
def get_current_commit(dir: str="./") -> str:
|
|
63
|
+
"""
|
|
64
|
+
### Rreturns the current commit short hash of a given repository:
|
|
65
|
+
|
|
66
|
+
#### Params:
|
|
67
|
+
- dir (str): Optional. Directory of repository.
|
|
68
|
+
|
|
69
|
+
#### Returns:
|
|
70
|
+
- (str): Current commit short hash, or empty string if it is not a repo or there were errors.
|
|
71
|
+
"""
|
|
72
|
+
ret_code, output = shell_handler.run_shell_command("git rev-parse --short HEAD", cwd=dir)
|
|
73
|
+
if ret_code or not output:
|
|
74
|
+
return ''
|
|
75
|
+
return output
|
|
76
|
+
|
|
77
|
+
def get_commit_branch(commit: str, dir: str="./") -> str:
|
|
78
|
+
"""
|
|
79
|
+
### Returns the name of the commit branch. It can be a branch name or a release name.
|
|
80
|
+
|
|
81
|
+
#### Params:
|
|
82
|
+
- commit (str): Commit hash.
|
|
83
|
+
- dir (str): Optional. Directory to repository.
|
|
84
|
+
|
|
85
|
+
#### Returns:
|
|
86
|
+
- (str): Name of the commit branch or release.
|
|
87
|
+
"""
|
|
88
|
+
ret_code, output = shell_handler.run_shell_command(f"git name-rev {commit}", cwd=dir)
|
|
89
|
+
if ret_code or not output:
|
|
90
|
+
return ''
|
|
91
|
+
return output.replace(commit, "").replace(" ", "")
|
|
92
|
+
|
|
93
|
+
def branch_exists_in_repo(repo_url: str, branch: str) -> bool:
|
|
94
|
+
"""
|
|
95
|
+
### Checks if the given branch exists in the given repository.
|
|
96
|
+
|
|
97
|
+
#### Params:
|
|
98
|
+
- repo (str): Repository to check from.
|
|
99
|
+
- branch (str): Name of the branch to check for.
|
|
100
|
+
|
|
101
|
+
#### Returns:
|
|
102
|
+
- (bool): True if the branch exists, False otherwise.
|
|
103
|
+
"""
|
|
104
|
+
return __ref_exists_in_repo(repo_url, branch, True)
|
|
105
|
+
|
|
106
|
+
def tag_exists_in_repo(repo_url: str, tag: str) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
### Checks if the given tag exists in the given repository.
|
|
109
|
+
|
|
110
|
+
#### Params:
|
|
111
|
+
- repo_url (str): Repository to check from.
|
|
112
|
+
- tag (str): Name of the tag to check for.
|
|
113
|
+
|
|
114
|
+
#### Returns:
|
|
115
|
+
- (bool): True if the tag exists, False otherwise.
|
|
116
|
+
"""
|
|
117
|
+
return __ref_exists_in_repo(repo_url, tag, False)
|
|
118
|
+
|
|
119
|
+
def get_clonable_branch(repo_url: str, preferred_branch: str, viable_tag: str) -> Optional[str]:
|
|
120
|
+
"""
|
|
121
|
+
### Decides the target to be cloned from a given repository.
|
|
122
|
+
|
|
123
|
+
The preferred branch will be selected if exists,
|
|
124
|
+
followed in priority by the viable tag if provided.
|
|
125
|
+
Finally, if no branch could be selected, None is returned,
|
|
126
|
+
meaning that repository's default branch will be used.
|
|
127
|
+
|
|
128
|
+
#### Params:
|
|
129
|
+
- repo_url (str): Url of the repositori to be cloned.
|
|
130
|
+
- preferred_branch (str): Preferred branch to clone into.
|
|
131
|
+
- viable_tag (str): If exists, it is returned if branch does not.
|
|
132
|
+
|
|
133
|
+
#### Returns:
|
|
134
|
+
- (str): Name of the branch to clone the repository into.
|
|
135
|
+
"""
|
|
136
|
+
if preferred_branch and branch_exists_in_repo(repo_url, preferred_branch):
|
|
137
|
+
return preferred_branch
|
|
138
|
+
if viable_tag and tag_exists_in_repo(repo_url, viable_tag):
|
|
139
|
+
return viable_tag
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
def execute_git_command_for_source(command: str, source: str) -> Tuple[int, str]:
|
|
143
|
+
"""
|
|
144
|
+
### Executes the git command for a specific source.
|
|
145
|
+
|
|
146
|
+
#### Params:
|
|
147
|
+
- command (str): Command to execute on the source.
|
|
148
|
+
- source (str): The source repository name.
|
|
149
|
+
|
|
150
|
+
#### Returns:
|
|
151
|
+
- (tuple(int, str)): Tuple containing the return code and output message.
|
|
152
|
+
"""
|
|
153
|
+
source_path = paths.get_source_path(source)
|
|
154
|
+
if not os.path.exists(source_path):
|
|
155
|
+
logger(logger.yellow(
|
|
156
|
+
f"WARNING: Source {source} does not exist in path {source_path}. Skipping."
|
|
157
|
+
))
|
|
158
|
+
return 0, ""
|
|
159
|
+
|
|
160
|
+
return shell_handler.run_shell_command(
|
|
161
|
+
f"git {command}",
|
|
162
|
+
cwd=source_path,
|
|
163
|
+
show_output=True,
|
|
164
|
+
show_error=True
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def __ref_exists_in_repo(repo_url: str, ref: str, is_branch: bool) -> bool:
|
|
168
|
+
"""
|
|
169
|
+
### Checks if a given reference exists in the given repository.
|
|
170
|
+
|
|
171
|
+
#### Params:
|
|
172
|
+
- repo_url (str): Repository to check from.
|
|
173
|
+
- ref (str): Reference to check for.
|
|
174
|
+
- is_branch (bool): If True, the reference is a branch. If False, it is a tag.
|
|
175
|
+
|
|
176
|
+
#### Returns:
|
|
177
|
+
- (bool): True if the ref exists, False otherwise.
|
|
178
|
+
"""
|
|
179
|
+
ref_type = "heads" if is_branch else "tags"
|
|
180
|
+
ret_code, output = shell_handler.run_shell_command(
|
|
181
|
+
f"git ls-remote --{ref_type} {repo_url}.git refs/{ref_type}/{ref}"
|
|
182
|
+
)
|
|
183
|
+
if ret_code:
|
|
184
|
+
return False
|
|
185
|
+
return f"refs/{ref_type}/{ref}" in output
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""### Functions that interact with the shell."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
from typing import Tuple
|
|
8
|
+
|
|
9
|
+
from xmipp3_installer.application.logger.logger import logger
|
|
10
|
+
from xmipp3_installer.application.logger import errors
|
|
11
|
+
|
|
12
|
+
def run_shell_command(
|
|
13
|
+
cmd: str,
|
|
14
|
+
cwd: str='./',
|
|
15
|
+
show_command: bool=False,
|
|
16
|
+
show_output: bool=False,
|
|
17
|
+
show_error: bool=False,
|
|
18
|
+
substitute: bool=False
|
|
19
|
+
) -> Tuple[int, str]:
|
|
20
|
+
"""
|
|
21
|
+
### This function runs the given command.
|
|
22
|
+
|
|
23
|
+
#### Params:
|
|
24
|
+
- cmd (str): Command to run.
|
|
25
|
+
- cwd (str): Optional. Path to run the command from. Default is current directory.
|
|
26
|
+
- show_output (bool): Optional. If True, output is printed.
|
|
27
|
+
- show_error (bool): Optional. If True, errors are printed.
|
|
28
|
+
- show_command (bool): Optional. If True, command is printed in blue.
|
|
29
|
+
- substitute (bool): Optional. If True, output will replace previous line.
|
|
30
|
+
|
|
31
|
+
#### Returns:
|
|
32
|
+
- (int): Return code.
|
|
33
|
+
- (str): Output of the command, regardless of if it is an error or regular output.
|
|
34
|
+
"""
|
|
35
|
+
if show_command:
|
|
36
|
+
logger(logger.blue(cmd), substitute=substitute)
|
|
37
|
+
ret_code, output_str = __run_command(cmd, cwd=cwd)
|
|
38
|
+
|
|
39
|
+
if not ret_code and show_output:
|
|
40
|
+
logger(output_str, substitute=substitute)
|
|
41
|
+
|
|
42
|
+
if ret_code and show_error:
|
|
43
|
+
logger.log_error(output_str, ret_code=ret_code)
|
|
44
|
+
|
|
45
|
+
return ret_code, output_str
|
|
46
|
+
|
|
47
|
+
def run_shell_command_in_streaming(
|
|
48
|
+
cmd: str,
|
|
49
|
+
cwd: str='./',
|
|
50
|
+
show_output: bool=False,
|
|
51
|
+
show_error: bool=False,
|
|
52
|
+
substitute: bool=False
|
|
53
|
+
) -> int:
|
|
54
|
+
"""
|
|
55
|
+
### Runs the given command and shows its output as it is being generated.
|
|
56
|
+
|
|
57
|
+
#### Params:
|
|
58
|
+
- cmd (str): Command to run.
|
|
59
|
+
- cwd (str): Optional. Path to run the command from. Default is current directory.
|
|
60
|
+
- show_output (bool): Optional. If True, output is printed.
|
|
61
|
+
- show_error (bool): Optional. If True, errors are printed.
|
|
62
|
+
- substitute (bool): Optional. If True, output will replace previous line.
|
|
63
|
+
|
|
64
|
+
#### Returns:
|
|
65
|
+
- (int): Return code.
|
|
66
|
+
"""
|
|
67
|
+
logger(cmd)
|
|
68
|
+
process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
69
|
+
|
|
70
|
+
thread_out = threading.Thread(
|
|
71
|
+
target=logger.log_in_streaming,
|
|
72
|
+
args=(process.stdout,),
|
|
73
|
+
kwargs={"show_in_terminal": show_output, "substitute": substitute, "err": False}
|
|
74
|
+
)
|
|
75
|
+
thread_err = threading.Thread(
|
|
76
|
+
target=logger.log_in_streaming,
|
|
77
|
+
args=(process.stderr,),
|
|
78
|
+
kwargs={"show_in_terminal": show_error, "substitute": substitute, "err": True}
|
|
79
|
+
)
|
|
80
|
+
thread_out.start()
|
|
81
|
+
thread_err.start()
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
process.wait()
|
|
85
|
+
thread_out.join()
|
|
86
|
+
thread_err.join()
|
|
87
|
+
except KeyboardInterrupt:
|
|
88
|
+
process.returncode = errors.INTERRUPTED_ERROR
|
|
89
|
+
|
|
90
|
+
return process.returncode
|
|
91
|
+
|
|
92
|
+
def __run_command(cmd: str, cwd: str='./') -> Tuple[int, str]:
|
|
93
|
+
"""
|
|
94
|
+
### Runs the given shell command.
|
|
95
|
+
|
|
96
|
+
#### Params:
|
|
97
|
+
- cmd (str): Command to run.
|
|
98
|
+
- cwd (str): Optional. Path to run the command from.
|
|
99
|
+
|
|
100
|
+
#### Returns:
|
|
101
|
+
- (int): Return code of the operation.
|
|
102
|
+
- (str): Return message of the operation.
|
|
103
|
+
"""
|
|
104
|
+
process = subprocess.Popen(cmd, cwd=cwd, env=os.environ, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
105
|
+
try:
|
|
106
|
+
process.wait()
|
|
107
|
+
except KeyboardInterrupt:
|
|
108
|
+
return errors.INTERRUPTED_ERROR, ""
|
|
109
|
+
|
|
110
|
+
ret_code = process.returncode
|
|
111
|
+
output, err = process.communicate()
|
|
112
|
+
output_str = output.decode() if not ret_code and output else err.decode()
|
|
113
|
+
output_str = output_str[:-1] if output_str.endswith('\n') else output_str
|
|
114
|
+
return ret_code, output_str
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
from xmipp3_installer.installer import constants
|
|
6
|
+
from xmipp3_installer.shared import singleton
|
|
7
|
+
|
|
8
|
+
class VersionsManager(singleton.Singleton):
|
|
9
|
+
def __init__(self, version_file_path: str):
|
|
10
|
+
"""
|
|
11
|
+
### Constructor.
|
|
12
|
+
|
|
13
|
+
Initializes the version manager by reading and validating version information from a JSON file.
|
|
14
|
+
|
|
15
|
+
#### Params:
|
|
16
|
+
- version_file_path (str): Path to the JSON file containing version information.
|
|
17
|
+
|
|
18
|
+
#### Raises:
|
|
19
|
+
- ValueError: If version numbers don't follow semver format (x.y.z) or release date isn't dd/mm/yyyy.
|
|
20
|
+
"""
|
|
21
|
+
super().__init__()
|
|
22
|
+
self.version_file_path = version_file_path
|
|
23
|
+
version_info = self.__get_version_info()
|
|
24
|
+
self.xmipp_version_number = version_info[constants.XMIPP]["version_number"]
|
|
25
|
+
self.xmipp_version_name = version_info[constants.XMIPP]["version_name"]
|
|
26
|
+
self.xmipp_release_date = version_info[constants.XMIPP]["release_date"]
|
|
27
|
+
self.sources_versions = version_info["sources_target_tag"]
|
|
28
|
+
self.__validate_fields()
|
|
29
|
+
|
|
30
|
+
def __get_version_info(self) -> Dict[str, str]:
|
|
31
|
+
"""
|
|
32
|
+
### Retrieves the version info from the version information JSON file.
|
|
33
|
+
|
|
34
|
+
#### Returns:
|
|
35
|
+
- (dict(str, str)): Dictionary containing the parsed values.
|
|
36
|
+
"""
|
|
37
|
+
with open(self.version_file_path) as json_data:
|
|
38
|
+
version_info = json.load(json_data)
|
|
39
|
+
return version_info
|
|
40
|
+
|
|
41
|
+
def __validate_fields(self):
|
|
42
|
+
"""
|
|
43
|
+
### Validates version numbers and release date format.
|
|
44
|
+
|
|
45
|
+
Checks that:
|
|
46
|
+
- Version numbers follow semantic versioning (x.y.z)
|
|
47
|
+
- Release date follows dd/mm/yyyy format
|
|
48
|
+
|
|
49
|
+
#### Raises:
|
|
50
|
+
- ValueError: If any field doesn't match its required format.
|
|
51
|
+
"""
|
|
52
|
+
self.__validate_version_number()
|
|
53
|
+
self.__validate_release_date()
|
|
54
|
+
|
|
55
|
+
def __validate_version_number(self):
|
|
56
|
+
"""
|
|
57
|
+
### Validates that version numbers follow semantic versioning format.
|
|
58
|
+
|
|
59
|
+
Checks that version numbers are in x.y.z format where x, y, z are integers.
|
|
60
|
+
|
|
61
|
+
#### Raises:
|
|
62
|
+
- ValueError: If any version number doesn't follow the required format.
|
|
63
|
+
"""
|
|
64
|
+
parts = self.xmipp_version_number.split('.')
|
|
65
|
+
if not self.__is_valid_semver(parts):
|
|
66
|
+
raise ValueError(
|
|
67
|
+
f"Version number '{self.xmipp_version_number}' is invalid. Must be three numbers separated by dots (x.y.z)."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def __is_valid_semver(self, version_parts: List[str]) -> bool:
|
|
71
|
+
"""
|
|
72
|
+
### Checks if version parts constitute a valid semantic version.
|
|
73
|
+
|
|
74
|
+
#### Params:
|
|
75
|
+
- version_parts (list(str)): List of strings representing version number parts.
|
|
76
|
+
|
|
77
|
+
#### Returns:
|
|
78
|
+
- (bool): True if version follows semver format, False otherwise.
|
|
79
|
+
"""
|
|
80
|
+
return len(version_parts) == 3 and all(part.isdigit() for part in version_parts)
|
|
81
|
+
|
|
82
|
+
def __validate_release_date(self):
|
|
83
|
+
"""
|
|
84
|
+
### Validates that release date follows dd/mm/yyyy format.
|
|
85
|
+
|
|
86
|
+
Checks that:
|
|
87
|
+
- Date string follows dd/mm/yyyy format
|
|
88
|
+
- Date is a valid calendar date
|
|
89
|
+
|
|
90
|
+
#### Raises:
|
|
91
|
+
- ValueError: If release date doesn't follow the required format or is invalid.
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
datetime.strptime(self.xmipp_release_date, "%d/%m/%Y")
|
|
95
|
+
except ValueError:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"Release date '{self.xmipp_release_date}' is invalid. Must be in dd/mm/yyyy format."
|
|
98
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from xmipp3_installer.api_client import api_client
|
|
4
|
+
from xmipp3_installer.api_client.assembler import installation_info_assembler
|
|
5
|
+
from xmipp3_installer.application.cli.arguments import modes
|
|
6
|
+
from xmipp3_installer.application.logger import errors
|
|
7
|
+
from xmipp3_installer.application.logger.logger import logger
|
|
8
|
+
from xmipp3_installer.installer import constants
|
|
9
|
+
from xmipp3_installer.installer.constants import paths
|
|
10
|
+
from xmipp3_installer.installer.handlers import versions_manager
|
|
11
|
+
from xmipp3_installer.installer.modes.mode_executor import ModeExecutor
|
|
12
|
+
from xmipp3_installer.application.logger import predefined_messages
|
|
13
|
+
from xmipp3_installer.installer.modes import mode_selector
|
|
14
|
+
from xmipp3_installer.repository import config
|
|
15
|
+
from xmipp3_installer.repository.config_vars import variables
|
|
16
|
+
|
|
17
|
+
class InstallationManager:
|
|
18
|
+
def __init__(self, args: Dict):
|
|
19
|
+
"""
|
|
20
|
+
### Constructor.
|
|
21
|
+
|
|
22
|
+
#### Params:
|
|
23
|
+
- args (dict): Dictionary containing all parsed command-line arguments.
|
|
24
|
+
"""
|
|
25
|
+
self.mode = args.pop(modes.MODE, modes.MODE_ALL)
|
|
26
|
+
config_handler = config.ConfigurationFileHandler(path=paths.CONFIG_FILE, show_errors=False)
|
|
27
|
+
self.context = {
|
|
28
|
+
**args,
|
|
29
|
+
**config_handler.values,
|
|
30
|
+
variables.LAST_MODIFIED_KEY: config_handler.last_modified,
|
|
31
|
+
constants.VERSIONS_CONTEXT_KEY: versions_manager.VersionsManager(paths.VERSION_INFO_FILE)
|
|
32
|
+
}
|
|
33
|
+
self.mode_executor: ModeExecutor = mode_selector.MODE_EXECUTORS[self.mode](self.context)
|
|
34
|
+
|
|
35
|
+
def run_installer(self):
|
|
36
|
+
"""
|
|
37
|
+
### Runs the installer with the given arguments.
|
|
38
|
+
|
|
39
|
+
#### Returns:
|
|
40
|
+
- (int): Return code.
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
ret_code, output = self.mode_executor.run()
|
|
44
|
+
except KeyboardInterrupt:
|
|
45
|
+
logger.log_error("", ret_code=errors.INTERRUPTED_ERROR, add_portal_link=False)
|
|
46
|
+
return errors.INTERRUPTED_ERROR
|
|
47
|
+
if ret_code:
|
|
48
|
+
logger.log_error(output, ret_code=ret_code, add_portal_link=ret_code != errors.INTERRUPTED_ERROR)
|
|
49
|
+
if (
|
|
50
|
+
self.mode_executor.sends_installation_info and
|
|
51
|
+
self.context[variables.SEND_INSTALLATION_STATISTICS]
|
|
52
|
+
):
|
|
53
|
+
logger("Sending anonymous installation info...")
|
|
54
|
+
api_client.send_installation_attempt(
|
|
55
|
+
installation_info_assembler.get_installation_info(
|
|
56
|
+
self.context[constants.VERSIONS_CONTEXT_KEY],
|
|
57
|
+
ret_code=ret_code
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
if not ret_code and self.mode_executor.prints_banner_on_exit:
|
|
61
|
+
logger(predefined_messages.get_success_message(
|
|
62
|
+
self.context[constants.VERSIONS_CONTEXT_KEY].xmipp_version_number
|
|
63
|
+
))
|
|
64
|
+
logger.close()
|
|
65
|
+
return ret_code
|
|
66
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""### Contains the executors for each mode."""
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from typing import Tuple, Dict
|
|
2
|
+
|
|
3
|
+
from xmipp3_installer.application.cli.arguments import params
|
|
4
|
+
from xmipp3_installer.application.logger.logger import logger
|
|
5
|
+
from xmipp3_installer.installer.modes import (
|
|
6
|
+
mode_executor, mode_config_executor, mode_get_sources_executor
|
|
7
|
+
)
|
|
8
|
+
from xmipp3_installer.installer.modes.mode_cmake import (
|
|
9
|
+
mode_config_build_executor, mode_compile_and_install_executor
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
class ModeAllExecutor(mode_executor.ModeExecutor):
|
|
13
|
+
def __init__(self, context: Dict):
|
|
14
|
+
"""
|
|
15
|
+
### Constructor.
|
|
16
|
+
|
|
17
|
+
#### Params:
|
|
18
|
+
- context (dict): Dictionary containing the installation context variables.
|
|
19
|
+
"""
|
|
20
|
+
config_executor = mode_config_executor.ModeConfigExecutor(
|
|
21
|
+
{**context, params.PARAM_OVERWRITE: False}
|
|
22
|
+
)
|
|
23
|
+
get_sources_executor = mode_get_sources_executor.ModeGetSourcesExecutor(
|
|
24
|
+
context
|
|
25
|
+
)
|
|
26
|
+
config_build_executor = mode_config_build_executor.ModeConfigBuildExecutor(
|
|
27
|
+
context
|
|
28
|
+
)
|
|
29
|
+
compile_and_install_executor = mode_compile_and_install_executor.ModeCompileAndInstallExecutor(
|
|
30
|
+
{**context, params.PARAM_BRANCH: None}
|
|
31
|
+
)
|
|
32
|
+
self.executors = [
|
|
33
|
+
config_executor,
|
|
34
|
+
get_sources_executor,
|
|
35
|
+
config_build_executor,
|
|
36
|
+
compile_and_install_executor
|
|
37
|
+
]
|
|
38
|
+
super().__init__(context)
|
|
39
|
+
|
|
40
|
+
def _set_executor_config(self):
|
|
41
|
+
"""
|
|
42
|
+
### Sets the specific executor params for this mode.
|
|
43
|
+
"""
|
|
44
|
+
super()._set_executor_config()
|
|
45
|
+
self.logs_to_file = True
|
|
46
|
+
self.prints_with_substitution = True
|
|
47
|
+
self.prints_banner_on_exit = True
|
|
48
|
+
self.sends_installation_info = True
|
|
49
|
+
|
|
50
|
+
def run(self) -> Tuple[int, str]:
|
|
51
|
+
"""
|
|
52
|
+
### Runs the whole installation process with the appropiate params.
|
|
53
|
+
|
|
54
|
+
#### Returns:
|
|
55
|
+
- (tuple(int, str)): Tuple containing the error status and an error message if there was an error.
|
|
56
|
+
"""
|
|
57
|
+
for executor_index in range(len(self.executors)):
|
|
58
|
+
if executor_index != 0:
|
|
59
|
+
logger("")
|
|
60
|
+
ret_code, output = self.executors[executor_index].run()
|
|
61
|
+
if ret_code:
|
|
62
|
+
return ret_code, output
|
|
63
|
+
return 0, ""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""### Contains the executors for the cleaning modes."""
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from xmipp3_installer.application.logger.logger import logger
|
|
4
|
+
from xmipp3_installer.installer import constants
|
|
5
|
+
from xmipp3_installer.installer.constants import paths
|
|
6
|
+
from xmipp3_installer.installer.modes.mode_clean import mode_clean_executor
|
|
7
|
+
|
|
8
|
+
class ModeCleanAllExecutor(mode_clean_executor.ModeCleanExecutor):
|
|
9
|
+
def _get_paths_to_delete(self) -> List[str]:
|
|
10
|
+
"""
|
|
11
|
+
### Returns a list of all the paths to be deleted.
|
|
12
|
+
|
|
13
|
+
#### Returns:
|
|
14
|
+
- (list(str)): List containing all the paths to delete.
|
|
15
|
+
"""
|
|
16
|
+
return [
|
|
17
|
+
*[paths.get_source_path(source) for source in constants.XMIPP_SOURCES],
|
|
18
|
+
paths.INSTALL_PATH,
|
|
19
|
+
paths.BUILD_PATH,
|
|
20
|
+
paths.CONFIG_FILE
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
def _get_confirmation_keyword(self) -> str:
|
|
24
|
+
"""
|
|
25
|
+
### Returns the keyword needed to be introduced by the user to confirm an operation.
|
|
26
|
+
|
|
27
|
+
#### Returns:
|
|
28
|
+
- (str): Confirmation keyword.
|
|
29
|
+
"""
|
|
30
|
+
return "YeS"
|
|
31
|
+
|
|
32
|
+
def _get_confirmation_message(self) -> str:
|
|
33
|
+
"""
|
|
34
|
+
### Returns message to be printed when asking for user confirmation.
|
|
35
|
+
|
|
36
|
+
#### Returns:
|
|
37
|
+
- (str): Confirmation message.
|
|
38
|
+
"""
|
|
39
|
+
return '\n'.join([
|
|
40
|
+
logger.yellow("WARNING: This will DELETE ALL content from src and build, and also the xmipp.conf file."),
|
|
41
|
+
logger.yellow("\tNotice that if you have unpushed changes, they will be deleted."),
|
|
42
|
+
logger.yellow(f"\nIf you are sure you want to do this, type '{self._get_confirmation_keyword()}' (case sensitive):")
|
|
43
|
+
])
|
|
44
|
+
|