ScriptCollection 3.5.165__py3-none-any.whl → 4.0.11__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.
@@ -1,3622 +0,0 @@
1
- from datetime import datetime, timedelta, timezone
2
- from graphlib import TopologicalSorter
3
- import os
4
- from pathlib import Path
5
- from functools import cmp_to_key
6
- import shutil
7
- import math
8
- import tarfile
9
- import re
10
- import urllib.request
11
- import zipfile
12
- import json
13
- import configparser
14
- import tempfile
15
- import uuid
16
- import yaml
17
- import requests
18
- from packaging import version
19
- import xmlschema
20
- from OpenSSL import crypto
21
- from lxml import etree
22
- from .GeneralUtilities import GeneralUtilities
23
- from .ScriptCollectionCore import ScriptCollectionCore
24
- from .SCLog import SCLog, LogLevel
25
- from .ProgramRunnerEpew import ProgramRunnerEpew
26
- from .ImageUpdater import ImageUpdater, VersionEcholon
27
-
28
-
29
- class CreateReleaseConfiguration():
30
- projectname: str
31
- remotename: str
32
- artifacts_folder: str
33
- push_artifacts_scripts_folder: str
34
- verbosity: int
35
- reference_repository_remote_name: str = None
36
- reference_repository_branch_name: str = "main"
37
- build_repository_branch: str = "main"
38
- public_repository_url: str
39
- additional_arguments_file: str = None
40
- repository_folder_name: str = None
41
- repository_folder: str = None
42
- __sc: ScriptCollectionCore = None
43
-
44
- def __init__(self, projectname: str, remotename: str, build_artifacts_target_folder: str, push_artifacts_scripts_folder: str, verbosity: int, repository_folder: str, additional_arguments_file: str, repository_folder_name: str):
45
- self.__sc = ScriptCollectionCore()
46
- self.projectname = projectname
47
- self.remotename = remotename
48
- self.artifacts_folder = build_artifacts_target_folder
49
- self.push_artifacts_scripts_folder = push_artifacts_scripts_folder
50
- self.verbosity = verbosity
51
- if self.remotename is None:
52
- self.public_repository_url = None
53
- else:
54
- self.public_repository_url = self.__sc.git_get_remote_url(repository_folder, remotename)
55
- self.reference_repository_remote_name = self.remotename
56
- self.additional_arguments_file = additional_arguments_file
57
- self.repository_folder = repository_folder
58
- self.repository_folder_name = repository_folder_name
59
-
60
-
61
- class CreateReleaseInformationForProjectInCommonProjectFormat:
62
- projectname: str
63
- repository: str
64
- artifacts_folder: str
65
- verbosity: int = 1
66
- reference_repository: str = None
67
- public_repository_url: str = None
68
- target_branch_name: str = None
69
- push_artifacts_scripts_folder: str = None
70
- target_environmenttype_for_qualitycheck: str = "QualityCheck"
71
- target_environmenttype_for_productive: str = "Productive"
72
- additional_arguments_file: str = None
73
- export_target: str = None
74
-
75
- def __init__(self, repository: str, artifacts_folder: str, projectname: str, public_repository_url: str, target_branch_name: str, additional_arguments_file: str, export_target: str, push_artifacts_scripts_folder: str):
76
- self.repository = repository
77
- self.public_repository_url = public_repository_url
78
- self.target_branch_name = target_branch_name
79
- self.artifacts_folder = artifacts_folder
80
- self.additional_arguments_file = additional_arguments_file
81
- self.export_target = export_target
82
- self.push_artifacts_scripts_folder = push_artifacts_scripts_folder
83
- if projectname is None:
84
- projectname = os.path.basename(self.repository)
85
- else:
86
- self.projectname = projectname
87
- self.reference_repository = f"{repository}Reference"
88
-
89
-
90
- class MergeToStableBranchInformationForProjectInCommonProjectFormat:
91
- repository: str
92
- sourcebranch: str = "main"
93
- targetbranch: str = "stable"
94
- sign_git_tags: bool = True
95
- target_environmenttype_for_qualitycheck: str = "QualityCheck"
96
- target_environmenttype_for_productive: str = "Productive"
97
- additional_arguments_file: str = None
98
- export_target: str = None
99
-
100
- push_source_branch: bool = False
101
- push_source_branch_remote_name: str = None
102
- push_target_branch: bool = False
103
- push_target_branch_remote_name: str = None
104
-
105
- verbosity: int = 1
106
-
107
- def __init__(self, repository: str, additional_arguments_file: str, export_target: str):
108
- self.repository = repository
109
- self.additional_arguments_file = additional_arguments_file
110
- self.export_target = export_target
111
-
112
-
113
- class TasksForCommonProjectStructure:
114
- __sc: ScriptCollectionCore = None
115
- reference_latest_version_of_xsd_when_generating_xml: bool = True
116
- validate_developers_of_repository: bool = True
117
- dotnet_runsettings_file = "runsettings.xml"
118
-
119
- def __init__(self, sc: ScriptCollectionCore = None):
120
- if sc is None:
121
- log: SCLog = SCLog()
122
- log.loglevel = LogLevel.Information
123
- sc = ScriptCollectionCore()
124
- sc.log = log
125
- self.__sc = sc
126
-
127
- @GeneralUtilities.check_arguments
128
- def is_codeunit_folder(self, codeunit_folder: str) -> bool:
129
- repo_folder = GeneralUtilities.resolve_relative_path("..", codeunit_folder)
130
- if not self.__sc.is_git_repository(repo_folder):
131
- return False
132
- codeunit_name = os.path.basename(codeunit_folder)
133
- codeunit_file: str = os.path.join(codeunit_folder, f"{codeunit_name}.codeunit.xml")
134
- if not os.path.isfile(codeunit_file):
135
- return False
136
- return True
137
-
138
- @GeneralUtilities.check_arguments
139
- def assert_is_codeunit_folder(self, codeunit_folder: str) -> str:
140
- repo_folder = GeneralUtilities.resolve_relative_path("..", codeunit_folder)
141
- if not self.__sc.is_git_repository(repo_folder):
142
- raise ValueError(f"'{codeunit_folder}' can not be a valid codeunit-folder because '{repo_folder}' is not a git-repository.")
143
- codeunit_name = os.path.basename(codeunit_folder)
144
- codeunit_file: str = os.path.join(codeunit_folder, f"{codeunit_name}.codeunit.xml")
145
- if not os.path.isfile(codeunit_file):
146
- raise ValueError(f"'{codeunit_folder}' is no codeunit-folder because '{codeunit_file}' does not exist.")
147
-
148
- @staticmethod
149
- @GeneralUtilities.check_arguments
150
- def get_development_environment_name() -> str:
151
- return "Development"
152
-
153
- @staticmethod
154
- @GeneralUtilities.check_arguments
155
- def get_qualitycheck_environment_name() -> str:
156
- return "QualityCheck"
157
-
158
- @staticmethod
159
- @GeneralUtilities.check_arguments
160
- def get_productive_environment_name() -> str:
161
- return "Productive"
162
-
163
- @GeneralUtilities.check_arguments
164
- def get_build_folder(self, repository_folder: str, codeunit_name: str) -> str:
165
- self.__sc.assert_is_git_repository(repository_folder)
166
- return os.path.join(repository_folder, codeunit_name, "Other", "Build")
167
-
168
- @GeneralUtilities.check_arguments
169
- def get_artifacts_folder(self, repository_folder: str, codeunit_name: str) -> str:
170
- self.__sc.assert_is_git_repository(repository_folder)
171
- return os.path.join(repository_folder, codeunit_name, "Other", "Artifacts")
172
-
173
- @GeneralUtilities.check_arguments
174
- def get_wheel_file(self, repository_folder: str, codeunit_name: str) -> str:
175
- self.__sc.assert_is_git_repository(repository_folder)
176
- return self.__sc.find_file_by_extension(os.path.join(self.get_artifacts_folder(repository_folder, codeunit_name), "BuildResult_Wheel"), "whl")
177
-
178
- @GeneralUtilities.check_arguments
179
- def get_testcoverage_threshold_from_codeunit_file(self, codeunit_file: str):
180
- root: etree._ElementTree = etree.parse(codeunit_file)
181
- return float(str(root.xpath('//cps:properties/cps:testsettings/@minimalcodecoverageinpercent', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'})[0]))
182
-
183
- @GeneralUtilities.check_arguments
184
- def codeunit_has_testable_sourcecode(self, codeunit_file: str) -> bool:
185
- root: etree._ElementTree = etree.parse(codeunit_file)
186
- return GeneralUtilities.string_to_boolean(str(root.xpath('//cps:properties/@codeunithastestablesourcecode', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'})[0]))
187
-
188
- @GeneralUtilities.check_arguments
189
- def codeunit_throws_exception_if_codeunitfile_is_not_validatable(self, codeunit_file: str) -> bool:
190
- root: etree._ElementTree = etree.parse(codeunit_file)
191
- return GeneralUtilities.string_to_boolean(str(root.xpath('//cps:properties/@throwexceptionifcodeunitfilecannotbevalidated', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'})[0]))
192
-
193
- @GeneralUtilities.check_arguments
194
- def codeunit_has_updatable_dependencies(self, codeunit_file: str) -> bool:
195
- root: etree._ElementTree = etree.parse(codeunit_file)
196
- return GeneralUtilities.string_to_boolean(str(root.xpath('//cps:properties/@codeunithasupdatabledependencies', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'})[0]))
197
-
198
- @GeneralUtilities.check_arguments
199
- def get_codeunit_description(self, codeunit_file: str) -> bool:
200
- root: etree._ElementTree = etree.parse(codeunit_file)
201
- return str(root.xpath('//cps:properties/@description', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'})[0])
202
-
203
- @GeneralUtilities.check_arguments
204
- def check_testcoverage(self, testcoverage_file_in_cobertura_format: str, repository_folder: str, codeunitname: str) -> None:
205
- self.__sc.assert_is_git_repository(repository_folder)
206
- GeneralUtilities.write_message_to_stdout("Check testcoverage..")
207
- root: etree._ElementTree = etree.parse(testcoverage_file_in_cobertura_format)
208
- if len(root.xpath('//coverage/packages/package')) != 1:
209
- raise ValueError(f"'{testcoverage_file_in_cobertura_format}' must contain exactly 1 package.")
210
- if root.xpath('//coverage/packages/package[1]/@name')[0] != codeunitname:
211
- raise ValueError(f"The package name of the tested package in '{testcoverage_file_in_cobertura_format}' must be '{codeunitname}'.")
212
- coverage_in_percent = round(float(str(root.xpath('//coverage/packages/package[1]/@line-rate')[0]))*100, 2)
213
- technicalminimalrequiredtestcoverageinpercent = 0
214
- if not technicalminimalrequiredtestcoverageinpercent < coverage_in_percent:
215
- raise ValueError(f"The test-coverage of package '{codeunitname}' must be greater than {technicalminimalrequiredtestcoverageinpercent}%.")
216
- codeunit_file = os.path.join(repository_folder, codeunitname, f"{codeunitname}.codeunit.xml")
217
- minimalrequiredtestcoverageinpercent = self.get_testcoverage_threshold_from_codeunit_file(codeunit_file)
218
- if (coverage_in_percent < minimalrequiredtestcoverageinpercent):
219
- raise ValueError(f"The testcoverage for codeunit {codeunitname} must be {minimalrequiredtestcoverageinpercent}% or more but is {coverage_in_percent}%.")
220
-
221
- @GeneralUtilities.check_arguments
222
- def replace_version_in_python_file(self, file: str, new_version_value: str) -> None:
223
- GeneralUtilities.write_text_to_file(file, re.sub("version = \"\\d+\\.\\d+\\.\\d+\"", f"version = \"{new_version_value}\"", GeneralUtilities.read_text_from_file(file)))
224
-
225
- @GeneralUtilities.check_arguments
226
- def standardized_tasks_run_testcases_for_python_codeunit(self, run_testcases_file: str, generate_badges: bool, verbosity: int, targetenvironmenttype: str, commandline_arguments: list[str]) -> None:
227
- codeunitname: str = Path(os.path.dirname(run_testcases_file)).parent.parent.name
228
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
229
- repository_folder: str = str(Path(os.path.dirname(run_testcases_file)).parent.parent.parent.absolute())
230
- codeunit_folder = os.path.join(repository_folder, codeunitname)
231
- self.__sc.run_program("coverage", f"run -m pytest -s ./{codeunitname}Tests", codeunit_folder, verbosity=verbosity)
232
- self.__sc.run_program("coverage", "xml", codeunit_folder, verbosity=verbosity)
233
- coveragefolder = os.path.join(repository_folder, codeunitname, "Other/Artifacts/TestCoverage")
234
- GeneralUtilities.ensure_directory_exists(coveragefolder)
235
- coveragefile = os.path.join(coveragefolder, "TestCoverage.xml")
236
- GeneralUtilities.ensure_file_does_not_exist(coveragefile)
237
- os.rename(os.path.join(repository_folder, codeunitname, "coverage.xml"), coveragefile)
238
- self.run_testcases_common_post_task(repository_folder, codeunitname, verbosity, generate_badges, targetenvironmenttype, commandline_arguments)
239
-
240
- @GeneralUtilities.check_arguments
241
- def copy_source_files_to_output_directory(self, buildscript_file: str) -> None:
242
- GeneralUtilities.write_message_to_stdout("Copy sourcecode...")
243
- folder = os.path.dirname(os.path.realpath(buildscript_file))
244
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", folder)
245
- result = self.__sc.run_program_argsasarray("git", ["ls-tree", "-r", "HEAD", "--name-only"], codeunit_folder)
246
- files = [f for f in result[1].split('\n') if len(f) > 0]
247
- for file in files:
248
- full_source_file = os.path.join(codeunit_folder, file)
249
- if os.path.isfile(full_source_file):
250
- # Reson of isdir-check:
251
- # Prevent trying to copy files which are not exist.
252
- # Otherwise exceptions occurr because uncommitted deletions of files will result in an error here.
253
- target_file = os.path.join(codeunit_folder, "Other", "Artifacts", "SourceCode", file)
254
- target_folder = os.path.dirname(target_file)
255
- GeneralUtilities.ensure_directory_exists(target_folder)
256
- shutil.copyfile(full_source_file, target_file)
257
-
258
- @GeneralUtilities.check_arguments
259
- def standardized_tasks_build_for_dart_project_in_common_project_structure(self, build_script_file: str, verbosity: int, targets: list[str], args: list[str], package_name: str = None):
260
- codeunit_folder = GeneralUtilities.resolve_relative_path("../../..", build_script_file)
261
- codeunit_name = os.path.basename(codeunit_folder)
262
- src_folder: str = None
263
- if package_name is None:
264
- src_folder = codeunit_folder
265
- else:
266
- src_folder = GeneralUtilities.resolve_relative_path(package_name, codeunit_folder) # TODO replace packagename
267
- artifacts_folder = os.path.join(codeunit_folder, "Other", "Artifacts")
268
- verbosity = self.get_verbosity_from_commandline_arguments(args, verbosity)
269
- target_names: dict[str, str] = {
270
- "web": "WebApplication",
271
- "windows": "Windows",
272
- "ios": "IOS",
273
- "appbundle": "Android",
274
- }
275
- for target in targets:
276
- GeneralUtilities.write_message_to_stdout(f"Build flutter-codeunit {codeunit_name} for target {target_names[target]}...")
277
- self.run_with_epew("flutter", f"build {target}", src_folder, verbosity)
278
- if target == "web":
279
- web_relase_folder = os.path.join(src_folder, "build/web")
280
- web_folder = os.path.join(artifacts_folder, "BuildResult_WebApplication")
281
- GeneralUtilities.ensure_directory_does_not_exist(web_folder)
282
- GeneralUtilities.ensure_directory_exists(web_folder)
283
- GeneralUtilities.copy_content_of_folder(web_relase_folder, web_folder)
284
- elif target == "windows":
285
- windows_release_folder = os.path.join(src_folder, "build/windows/x64/runner/Release")
286
- windows_folder = os.path.join(artifacts_folder, "BuildResult_Windows")
287
- GeneralUtilities.ensure_directory_does_not_exist(windows_folder)
288
- GeneralUtilities.ensure_directory_exists(windows_folder)
289
- GeneralUtilities.copy_content_of_folder(windows_release_folder, windows_folder)
290
- elif target == "ios":
291
- raise ValueError("building for ios is not implemented yet")
292
- elif target == "appbundle":
293
- aab_folder = os.path.join(artifacts_folder, "BuildResult_AAB")
294
- GeneralUtilities.ensure_directory_does_not_exist(aab_folder)
295
- GeneralUtilities.ensure_directory_exists(aab_folder)
296
- aab_relase_folder = os.path.join(src_folder, "build/app/outputs/bundle/release")
297
- aab_file_original = self.__sc.find_file_by_extension(aab_relase_folder, "aab")
298
- aab_file = os.path.join(aab_folder, f"{codeunit_name}.aab")
299
- shutil.copyfile(aab_file_original, aab_file)
300
- bundletool = os.path.join(codeunit_folder, "Other/Resources/AndroidAppBundleTool/bundletool.jar")
301
- apk_folder = os.path.join(artifacts_folder, "BuildResult_APK")
302
- GeneralUtilities.ensure_directory_does_not_exist(apk_folder)
303
- GeneralUtilities.ensure_directory_exists(apk_folder)
304
- apks_file = f"{apk_folder}/{codeunit_name}.apks"
305
- self.__sc.run_program("java", f"-jar {bundletool} build-apks --bundle={aab_file} --output={apks_file} --mode=universal", aab_relase_folder, verbosity)
306
- with zipfile.ZipFile(apks_file, "r") as zip_ref:
307
- zip_ref.extract("universal.apk", apk_folder)
308
- GeneralUtilities.ensure_file_does_not_exist(apks_file)
309
- os.rename(f"{apk_folder}/universal.apk", f"{apk_folder}/{codeunit_name}.apk")
310
- else:
311
- raise ValueError(f"Not supported target: {target}")
312
- self.copy_source_files_to_output_directory(build_script_file)
313
-
314
- @GeneralUtilities.check_arguments
315
- def standardized_tasks_build_for_python_codeunit(self, buildscript_file: str, verbosity: int, targetenvironmenttype: str, commandline_arguments: list[str]) -> None:
316
- codeunitname: str = Path(os.path.dirname(buildscript_file)).parent.parent.name
317
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
318
- codeunit_folder = str(Path(os.path.dirname(buildscript_file)).parent.parent.absolute())
319
- repository_folder: str = str(Path(os.path.dirname(buildscript_file)).parent.parent.parent.absolute())
320
- target_directory = GeneralUtilities.resolve_relative_path("../Artifacts/BuildResult_Wheel", os.path.join(self.get_artifacts_folder(repository_folder, codeunitname)))
321
- GeneralUtilities.ensure_directory_exists(target_directory)
322
- self.__sc.run_program("python", f"-m build --wheel --outdir {target_directory}", codeunit_folder, verbosity=verbosity)
323
- self.generate_bom_for_python_project(verbosity, codeunit_folder, codeunitname, commandline_arguments)
324
- self.copy_source_files_to_output_directory(buildscript_file)
325
-
326
- @GeneralUtilities.check_arguments
327
- def generate_bom_for_python_project(self, verbosity: int, codeunit_folder: str, codeunitname: str, commandline_arguments: list[str]) -> None:
328
- self.assert_is_codeunit_folder(codeunit_folder)
329
- repository_folder = os.path.dirname(codeunit_folder)
330
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
331
- codeunitversion = self.get_version_of_codeunit_folder(codeunit_folder)
332
- bom_folder = "Other/Artifacts/BOM"
333
- bom_folder_full = os.path.join(codeunit_folder, bom_folder)
334
- GeneralUtilities.ensure_directory_exists(bom_folder_full)
335
- if not os.path.isfile(os.path.join(codeunit_folder, "requirements.txt")):
336
- raise ValueError(f"Codeunit {codeunitname} does not have a 'requirements.txt'-file.")
337
- # TODO check that all values from setup.cfg are contained in requirements.txt
338
- result = self.__sc.run_program("cyclonedx-py", "requirements", codeunit_folder, verbosity=verbosity)
339
- bom_file_relative_json = f"{bom_folder}/{codeunitname}.{codeunitversion}.bom.json"
340
- bom_file_relative_xml = f"{bom_folder}/{codeunitname}.{codeunitversion}.bom.xml"
341
- bom_file_json = os.path.join(codeunit_folder, bom_file_relative_json)
342
- bom_file_xml = os.path.join(codeunit_folder, bom_file_relative_xml)
343
-
344
- GeneralUtilities.ensure_file_exists(bom_file_json)
345
- GeneralUtilities.write_text_to_file(bom_file_json, result[1])
346
- self.ensure_cyclonedxcli_is_available(repository_folder)
347
- cyclonedx_exe = os.path.join(repository_folder, "Other/Resources/CycloneDXCLI/cyclonedx-cli")
348
- if GeneralUtilities.current_system_is_windows():
349
- cyclonedx_exe = cyclonedx_exe+".exe"
350
- self.__sc.run_program(cyclonedx_exe, f"convert --input-file ./{codeunitname}/{bom_file_relative_json} --input-format json --output-file ./{codeunitname}/{bom_file_relative_xml} --output-format xml", repository_folder)
351
- self.__sc.format_xml_file(bom_file_xml)
352
- GeneralUtilities.ensure_file_does_not_exist(bom_file_json)
353
-
354
- @GeneralUtilities.check_arguments
355
- def standardized_tasks_push_wheel_file_to_registry(self, wheel_file: str, api_key: str, repository: str, gpg_identity: str, verbosity: int) -> None:
356
- # repository-value when PyPi should be used: "pypi"
357
- # gpg_identity-value when wheel-file should not be signed: None
358
- folder = os.path.dirname(wheel_file)
359
- filename = os.path.basename(wheel_file)
360
-
361
- if gpg_identity is None:
362
- gpg_identity_argument = GeneralUtilities.empty_string
363
- else:
364
- gpg_identity_argument = GeneralUtilities.empty_string # f" --sign --identity {gpg_identity}"
365
- # disabled due to https://blog.pypi.org/posts/2023-05-23-removing-pgp/
366
-
367
- if verbosity > 2:
368
- verbose_argument = " --verbose"
369
- else:
370
- verbose_argument = GeneralUtilities.empty_string
371
-
372
- twine_argument = f"upload{gpg_identity_argument} --repository {repository} --non-interactive {filename} --disable-progress-bar"
373
- twine_argument = f"{twine_argument} --username __token__ --password {api_key}{verbose_argument}"
374
- self.__sc.run_program("twine", twine_argument, folder, verbosity=verbosity, throw_exception_if_exitcode_is_not_zero=True)
375
-
376
- @GeneralUtilities.check_arguments
377
- def push_wheel_build_artifact(self, push_build_artifacts_file, product_name, codeunitname, repository: str, apikey: str, gpg_identity: str, verbosity: int, commandline_arguments: list[str], repository_folder_name: str) -> None:
378
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
379
- folder_of_this_file = os.path.dirname(push_build_artifacts_file)
380
- repository_folder = GeneralUtilities.resolve_relative_path(f"..{os.path.sep}../Submodules{os.path.sep}{repository_folder_name}", folder_of_this_file)
381
- wheel_file = self.get_wheel_file(repository_folder, codeunitname)
382
- self.standardized_tasks_push_wheel_file_to_registry(wheel_file, apikey, repository, gpg_identity, verbosity)
383
-
384
- @GeneralUtilities.check_arguments
385
- def get_version_of_codeunit_file_content(self, codeunit_file_content: str) -> str:
386
- root: etree._ElementTree = etree.fromstring(codeunit_file_content.encode("utf-8"))
387
- result = str(root.xpath('//cps:version/text()', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'})[0])
388
- return result
389
-
390
- @GeneralUtilities.check_arguments
391
- def get_version_of_codeunit(self, codeunit_file: str) -> None:
392
- return self.get_version_of_codeunit_file_content(GeneralUtilities.read_text_from_file(codeunit_file))
393
-
394
- @GeneralUtilities.check_arguments
395
- def get_version_of_codeunit_folder(self, codeunit_folder: str) -> None:
396
- self.assert_is_codeunit_folder(codeunit_folder)
397
- codeunit_file = os.path.join(codeunit_folder, f"{os.path.basename(codeunit_folder)}.codeunit.xml")
398
- return self.get_version_of_codeunit(codeunit_file)
399
-
400
- @staticmethod
401
- @GeneralUtilities.check_arguments
402
- def get_string_value_from_commandline_arguments(commandline_arguments: list[str], property_name: str, default_value: str) -> str:
403
- result = TasksForCommonProjectStructure.get_property_from_commandline_arguments(commandline_arguments, property_name)
404
- if result is None:
405
- return default_value
406
- else:
407
- return result
408
-
409
- @staticmethod
410
- @GeneralUtilities.check_arguments
411
- def get_is_pre_merge_value_from_commandline_arguments(commandline_arguments: list[str], default_value: bool) -> bool:
412
- result = TasksForCommonProjectStructure.get_property_from_commandline_arguments(commandline_arguments, "is_pre_merge")
413
- if result is None:
414
- return default_value
415
- else:
416
- return GeneralUtilities.string_to_boolean(result)
417
-
418
- @staticmethod
419
- @GeneralUtilities.check_arguments
420
- def get_assume_dependent_codeunits_are_already_built_from_commandline_arguments(commandline_arguments: list[str], default_value: bool) -> bool:
421
- result = TasksForCommonProjectStructure.get_property_from_commandline_arguments(commandline_arguments, "assume_dependent_codeunits_are_already_built")
422
- if result is None:
423
- return default_value
424
- else:
425
- return GeneralUtilities.string_to_boolean(result)
426
-
427
- @staticmethod
428
- @GeneralUtilities.check_arguments
429
- def get_verbosity_from_commandline_arguments(commandline_arguments: list[str], default_value: int) -> int:
430
- result = TasksForCommonProjectStructure.get_property_from_commandline_arguments(commandline_arguments, "verbosity")
431
- if result is None:
432
- return default_value
433
- else:
434
- return int(result)
435
-
436
- @staticmethod
437
- @GeneralUtilities.check_arguments
438
- def get_targetenvironmenttype_from_commandline_arguments(commandline_arguments: list[str], default_value: str) -> str:
439
- result = TasksForCommonProjectStructure.get_property_from_commandline_arguments(commandline_arguments, "targetenvironmenttype")
440
- if result is None:
441
- return default_value
442
- else:
443
- return result
444
-
445
- @staticmethod
446
- @GeneralUtilities.check_arguments
447
- def get_additionalargumentsfile_from_commandline_arguments(commandline_arguments: list[str], default_value: str) -> str:
448
- result = TasksForCommonProjectStructure.get_property_from_commandline_arguments(commandline_arguments, "additionalargumentsfile")
449
- if result is None:
450
- return default_value
451
- else:
452
- return result
453
-
454
- @staticmethod
455
- @GeneralUtilities.check_arguments
456
- def get_filestosign_from_commandline_arguments(commandline_arguments: list[str], default_value: dict[str, str]) -> dict[str, str]:
457
- result_plain = TasksForCommonProjectStructure.get_property_from_commandline_arguments(commandline_arguments, "sign")
458
- if result_plain is None:
459
- return default_value
460
- else:
461
- result: dict[str, str] = dict[str, str]()
462
- files_tuples = GeneralUtilities.to_list(result_plain, ";")
463
- for files_tuple in files_tuples:
464
- splitted = files_tuple.split("=")
465
- result[splitted[0]] = splitted[1]
466
- return result
467
-
468
- @staticmethod
469
- @GeneralUtilities.check_arguments
470
- def get_property_from_commandline_arguments(commandline_arguments: list[str], property_name: str) -> str:
471
- result: str = None
472
- count = len(commandline_arguments)
473
- loop_index = -1
474
- for commandline_argument in commandline_arguments:
475
- loop_index = loop_index+1
476
- if loop_index < count-1:
477
- prefix = f"--overwrite_{property_name}"
478
- if commandline_argument == prefix:
479
- result = commandline_arguments[loop_index+1]
480
- return result
481
- return result
482
-
483
- @GeneralUtilities.check_arguments
484
- def update_version_of_codeunit(self, common_tasks_file: str, current_version: str) -> None:
485
- codeunit_name: str = os.path.basename(GeneralUtilities.resolve_relative_path("..", os.path.dirname(common_tasks_file)))
486
- codeunit_file: str = os.path.join(GeneralUtilities.resolve_relative_path("..", os.path.dirname(common_tasks_file)), f"{codeunit_name}.codeunit.xml")
487
- self.write_version_to_codeunit_file(codeunit_file, current_version)
488
-
489
- @GeneralUtilities.check_arguments
490
- def t4_transform(self, commontasks_script_file_of_current_file: str, verbosity: int, ignore_git_ignored_files: bool = True):
491
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", commontasks_script_file_of_current_file)
492
- self.__ensure_grylibrary_is_available(codeunit_folder)
493
- repository_folder: str = os.path.dirname(codeunit_folder)
494
- codeunitname: str = os.path.basename(codeunit_folder)
495
- codeunit_folder = os.path.join(repository_folder, codeunitname)
496
- for search_result in Path(codeunit_folder).glob('**/*.tt'):
497
- tt_file = str(search_result)
498
- relative_path_to_tt_file_from_repository = str(Path(tt_file).relative_to(repository_folder))
499
- if (not ignore_git_ignored_files) or (ignore_git_ignored_files and not self.__sc.file_is_git_ignored(relative_path_to_tt_file_from_repository, repository_folder)):
500
- relative_path_to_tt_file_from_codeunit_file = str(Path(tt_file).relative_to(codeunit_folder))
501
- argument = [f"--parameter=repositoryFolder={repository_folder}", f"--parameter=codeUnitName={codeunitname}", relative_path_to_tt_file_from_codeunit_file]
502
- self.__sc.run_program_argsasarray("t4", argument, codeunit_folder, verbosity=verbosity)
503
-
504
- @GeneralUtilities.check_arguments
505
- def get_resource_from_global_resource(self, codeunit_folder: str, resource_name: str):
506
- repository_folder: str = GeneralUtilities.resolve_relative_path("..", codeunit_folder)
507
- source_folder: str = os.path.join(repository_folder, "Other", "Resources", resource_name)
508
- target_folder: str = os.path.join(codeunit_folder, "Other", "Resources", resource_name)
509
- GeneralUtilities.ensure_folder_exists_and_is_empty(target_folder)
510
- GeneralUtilities.copy_content_of_folder(source_folder, target_folder)
511
-
512
- @GeneralUtilities.check_arguments
513
- def standardized_tasks_generate_reference_by_docfx(self, generate_reference_script_file: str, verbosity: int, targetenvironmenttype: str, commandline_arguments: list[str]) -> None:
514
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
515
- folder_of_current_file = os.path.dirname(generate_reference_script_file)
516
- generated_reference_folder = GeneralUtilities.resolve_relative_path("../Artifacts/Reference", folder_of_current_file)
517
- GeneralUtilities.ensure_directory_does_not_exist(generated_reference_folder)
518
- GeneralUtilities.ensure_directory_exists(generated_reference_folder)
519
- obj_folder = os.path.join(folder_of_current_file, "obj")
520
- GeneralUtilities.ensure_directory_does_not_exist(obj_folder)
521
- GeneralUtilities.ensure_directory_exists(obj_folder)
522
- self.__sc.run_program("docfx", "docfx.json", folder_of_current_file, verbosity=verbosity)
523
- # TODO generate also a darkmode-variant (darkFX for example, see https://dotnet.github.io/docfx/extensions/templates.html )
524
- GeneralUtilities.ensure_directory_does_not_exist(obj_folder)
525
-
526
- def standardized_task_verify_standard_format_csproj_files(self, codeunit_folder: str) -> bool:
527
- self.assert_is_codeunit_folder(codeunit_folder)
528
- repository_folder = os.path.dirname(codeunit_folder)
529
- codeunit_name = os.path.basename(codeunit_folder)
530
- codeunit_folder = os.path.join(repository_folder, codeunit_name)
531
- codeunit_version = self.get_version_of_codeunit_folder(codeunit_folder)
532
-
533
- csproj_project_name = codeunit_name
534
- csproj_file = os.path.join(codeunit_folder, csproj_project_name, csproj_project_name+".csproj")
535
- result1: tuple[bool, str, list[str]] = self.__standardized_task_verify_standard_format_for_project_csproj_file(csproj_file, codeunit_folder, codeunit_name, codeunit_version)
536
- if not result1[0]:
537
- hints: str = "\n".join(result1[2])
538
- raise ValueError(f"'{csproj_file}' with content '{GeneralUtilities.read_text_from_file(csproj_file)}' does not match the standardized .csproj-file-format which is defined by the regex '{result1[1]}'.\n{hints}")
539
-
540
- test_csproj_project_name = csproj_project_name+"Tests"
541
- test_csproj_file = os.path.join(codeunit_folder, test_csproj_project_name, test_csproj_project_name+".csproj")
542
- result2: tuple[bool, str, list[str]] = self.__standardized_task_verify_standard_format_for_test_csproj_file(test_csproj_file, codeunit_name, codeunit_version)
543
- if not result2[0]:
544
- hints: str = "\n".join(result2[2])
545
- raise ValueError(f"'{test_csproj_file}' with content '{GeneralUtilities.read_text_from_file(test_csproj_file)}' does not match the standardized .csproj-file-format which is defined by the regex '{result2[1]}'.\n{hints}")
546
-
547
- def __standardized_task_verify_standard_format_for_project_csproj_file(self, csproj_file: str, codeunit_folder: str, codeunit_name: str, codeunit_version: str) -> tuple[bool, str, str]:
548
- self.assert_is_codeunit_folder(codeunit_folder)
549
- codeunit_name_regex = re.escape(codeunit_name)
550
- codeunit_file = os.path.join(codeunit_folder, f"{codeunit_name}.codeunit.xml")
551
- codeunit_description = self.get_codeunit_description(codeunit_file)
552
- codeunit_version_regex = re.escape(codeunit_version)
553
- codeunit_description_regex = re.escape(codeunit_description)
554
- regex = f"""^<Project Sdk=\\"Microsoft\\.NET\\.Sdk\\">
555
- <PropertyGroup>
556
- <TargetFramework>([^<]+)<\\/TargetFramework>
557
- <Authors>([^<]+)<\\/Authors>
558
- <Version>{codeunit_version_regex}<\\/Version>
559
- <AssemblyVersion>{codeunit_version_regex}<\\/AssemblyVersion>
560
- <FileVersion>{codeunit_version_regex}<\\/FileVersion>
561
- <SelfContained>false<\\/SelfContained>
562
- <IsPackable>false<\\/IsPackable>
563
- <PreserveCompilationContext>false<\\/PreserveCompilationContext>
564
- <GenerateRuntimeConfigurationFiles>true<\\/GenerateRuntimeConfigurationFiles>
565
- <Copyright>([^<]+)<\\/Copyright>
566
- <Description>{codeunit_description_regex}<\\/Description>
567
- <PackageProjectUrl>https:\\/\\/([^<]+)<\\/PackageProjectUrl>
568
- <RepositoryUrl>https:\\/\\/([^<]+)\\.git<\\/RepositoryUrl>
569
- <RootNamespace>([^<]+)\\.Core<\\/RootNamespace>
570
- <ProduceReferenceAssembly>false<\\/ProduceReferenceAssembly>
571
- <Nullable>(disable|enable|warnings|annotations)<\\/Nullable>
572
- <Configurations>Development;QualityCheck;Productive<\\/Configurations>
573
- <IsTestProject>false<\\/IsTestProject>
574
- <LangVersion>([^<]+)<\\/LangVersion>
575
- <PackageRequireLicenseAcceptance>true<\\/PackageRequireLicenseAcceptance>
576
- <GenerateSerializationAssemblies>Off<\\/GenerateSerializationAssemblies>
577
- <AppendTargetFrameworkToOutputPath>false<\\/AppendTargetFrameworkToOutputPath>
578
- <OutputPath>\\.\\.\\\\Other\\\\Artifacts\\\\BuildResult_DotNet_win\\-x64<\\/OutputPath>
579
- <PlatformTarget>([^<]+)<\\/PlatformTarget>
580
- <WarningLevel>\\d<\\/WarningLevel>
581
- <Prefer32Bit>false<\\/Prefer32Bit>
582
- <SignAssembly>True<\\/SignAssembly>
583
- <AssemblyOriginatorKeyFile>\\.\\.\\\\\\.\\.\\\\Other\\\\Resources\\\\PublicKeys\\\\StronglyNamedKey\\\\([^<]+)PublicKey\\.snk<\\/AssemblyOriginatorKeyFile>
584
- <DelaySign>True<\\/DelaySign>
585
- <NoWarn>([^<]+)<\\/NoWarn>
586
- <WarningsAsErrors>([^<]+)<\\/WarningsAsErrors>
587
- <ErrorLog>\\.\\.\\\\Other\\\\Resources\\\\CodeAnalysisResult\\\\{codeunit_name_regex}\\.sarif<\\/ErrorLog>
588
- <OutputType>([^<]+)<\\/OutputType>
589
- <DocumentationFile>\\.\\.\\\\Other\\\\Artifacts\\\\MetaInformation\\\\{codeunit_name_regex}\\.xml<\\/DocumentationFile>(\\n|.)*
590
- <\\/PropertyGroup>
591
- <PropertyGroup Condition=\\\"'\\$\\(Configuration\\)'=='Development'\\\">
592
- <DebugType>full<\\/DebugType>
593
- <DebugSymbols>true<\\/DebugSymbols>
594
- <Optimize>false<\\/Optimize>
595
- <DefineConstants>TRACE;DEBUG;Development<\\/DefineConstants>
596
- <ErrorReport>prompt<\\/ErrorReport>
597
- <\\/PropertyGroup>
598
- <PropertyGroup Condition=\\\"'\\$\\(Configuration\\)'=='QualityCheck'\\\">
599
- <DebugType>portable<\\/DebugType>
600
- <DebugSymbols>true<\\/DebugSymbols>
601
- <Optimize>false<\\/Optimize>
602
- <DefineConstants>TRACE;QualityCheck<\\/DefineConstants>
603
- <ErrorReport>none<\\/ErrorReport>
604
- <\\/PropertyGroup>
605
- <PropertyGroup Condition=\\\"'\\$\\(Configuration\\)'=='Productive'\\\">
606
- <DebugType>none<\\/DebugType>
607
- <DebugSymbols>false<\\/DebugSymbols>
608
- <Optimize>true<\\/Optimize>
609
- <DefineConstants>Productive<\\/DefineConstants>
610
- <ErrorReport>none<\\/ErrorReport>
611
- <\\/PropertyGroup>(\\n|.)*
612
- <\\/Project>$"""
613
- result = self.__standardized_task_verify_standard_format_for_csproj_files(regex, csproj_file)
614
- return (result[0], regex, result[1])
615
-
616
- def __standardized_task_verify_standard_format_for_test_csproj_file(self, csproj_file: str, codeunit_name: str, codeunit_version: str) -> tuple[bool, str, str]:
617
- codeunit_name_regex = re.escape(codeunit_name)
618
- codeunit_version_regex = re.escape(codeunit_version)
619
- regex = f"""^<Project Sdk=\\"Microsoft\\.NET\\.Sdk\\">
620
- <PropertyGroup>
621
- <TargetFramework>([^<]+)<\\/TargetFramework>
622
- <Authors>([^<]+)<\\/Authors>
623
- <Version>{codeunit_version_regex}<\\/Version>
624
- <AssemblyVersion>{codeunit_version_regex}<\\/AssemblyVersion>
625
- <FileVersion>{codeunit_version_regex}<\\/FileVersion>
626
- <SelfContained>false<\\/SelfContained>
627
- <IsPackable>false<\\/IsPackable>
628
- <PreserveCompilationContext>false<\\/PreserveCompilationContext>
629
- <GenerateRuntimeConfigurationFiles>true<\\/GenerateRuntimeConfigurationFiles>
630
- <Copyright>([^<]+)<\\/Copyright>
631
- <Description>{codeunit_name_regex}Tests is the test-project for {codeunit_name_regex}\\.<\\/Description>
632
- <PackageProjectUrl>https:\\/\\/([^<]+)<\\/PackageProjectUrl>
633
- <RepositoryUrl>https:\\/\\/([^<]+)\\.git</RepositoryUrl>
634
- <RootNamespace>([^<]+)\\.Tests<\\/RootNamespace>
635
- <ProduceReferenceAssembly>false<\\/ProduceReferenceAssembly>
636
- <Nullable>(disable|enable|warnings|annotations)<\\/Nullable>
637
- <Configurations>Development;QualityCheck;Productive<\\/Configurations>
638
- <IsTestProject>true<\\/IsTestProject>
639
- <LangVersion>([^<]+)<\\/LangVersion>
640
- <PackageRequireLicenseAcceptance>true<\\/PackageRequireLicenseAcceptance>
641
- <GenerateSerializationAssemblies>Off<\\/GenerateSerializationAssemblies>
642
- <AppendTargetFrameworkToOutputPath>false<\\/AppendTargetFrameworkToOutputPath>
643
- <OutputPath>\\.\\.\\\\Other\\\\Artifacts\\\\BuildResultTests_DotNet_win\\-x64<\\/OutputPath>
644
- <PlatformTarget>([^<]+)<\\/PlatformTarget>
645
- <WarningLevel>\\d<\\/WarningLevel>
646
- <Prefer32Bit>false<\\/Prefer32Bit>
647
- <SignAssembly>true<\\/SignAssembly>
648
- <AssemblyOriginatorKeyFile>\\.\\.\\\\\\.\\.\\\\Other\\\\Resources\\\\PublicKeys\\\\StronglyNamedKey\\\\([^<]+)PublicKey\\.snk<\\/AssemblyOriginatorKeyFile>
649
- <DelaySign>true<\\/DelaySign>
650
- <NoWarn>([^<]+)<\\/NoWarn>
651
- <WarningsAsErrors>([^<]+)<\\/WarningsAsErrors>
652
- <ErrorLog>\\.\\.\\\\Other\\\\Resources\\\\CodeAnalysisResult\\\\{codeunit_name_regex}Tests\\.sarif<\\/ErrorLog>
653
- <OutputType>Library<\\/OutputType>(\\n|.)*
654
- <\\/PropertyGroup>
655
- <PropertyGroup Condition=\\\"'\\$\\(Configuration\\)'=='Development'\\\">
656
- <DebugType>full<\\/DebugType>
657
- <DebugSymbols>true<\\/DebugSymbols>
658
- <Optimize>false<\\/Optimize>
659
- <DefineConstants>TRACE;DEBUG;Development<\\/DefineConstants>
660
- <ErrorReport>prompt<\\/ErrorReport>
661
- <\\/PropertyGroup>
662
- <PropertyGroup Condition=\\\"'\\$\\(Configuration\\)'=='QualityCheck'\\\">
663
- <DebugType>portable<\\/DebugType>
664
- <DebugSymbols>true<\\/DebugSymbols>
665
- <Optimize>false<\\/Optimize>
666
- <DefineConstants>TRACE;QualityCheck<\\/DefineConstants>
667
- <ErrorReport>none<\\/ErrorReport>
668
- <\\/PropertyGroup>
669
- <PropertyGroup Condition=\\\"'\\$\\(Configuration\\)'=='Productive'\\\">
670
- <DebugType>none<\\/DebugType>
671
- <DebugSymbols>false<\\/DebugSymbols>
672
- <Optimize>true<\\/Optimize>
673
- <DefineConstants>Productive<\\/DefineConstants>
674
- <ErrorReport>none<\\/ErrorReport>
675
- <\\/PropertyGroup>(\\n|.)*
676
- <\\/Project>$"""
677
- result = self.__standardized_task_verify_standard_format_for_csproj_files(regex, csproj_file)
678
- return (result[0], regex, result[1])
679
-
680
- def __standardized_task_verify_standard_format_for_csproj_files(self, regex: str, csproj_file: str) -> tuple[bool, list[str]]:
681
- filename = os.path.basename(csproj_file)
682
- GeneralUtilities.write_message_to_stdout(f"Check {filename}...")
683
- file_content = GeneralUtilities.read_text_from_file(csproj_file)
684
- regex_for_check = regex.replace("\r", GeneralUtilities.empty_string).replace("\n", "\\n")
685
- file_content = file_content.replace("\r", GeneralUtilities.empty_string)
686
- match = re.match(regex_for_check, file_content)
687
- result = match is not None
688
- hints = None
689
- if not result:
690
- hints = self.get_hints_for_csproj(regex,file_content)
691
- return (result, hints)
692
-
693
- @GeneralUtilities.check_arguments
694
- def get_hints_for_csproj(self,regex:str,file_content:str) -> list[str]:
695
- result: list[str] = []
696
- strings = GeneralUtilities.string_to_lines(regex)
697
- regexes = GeneralUtilities.string_to_lines(file_content)
698
- amount_of_lines = len(regexes)
699
- if len(strings) < amount_of_lines:
700
- result.append("csproj-file has less lines than the regex requires.")
701
- return result
702
- for i in range(amount_of_lines - 1):
703
- s = strings[i]
704
- r = regexes[i]
705
- if not re.match(r, s):
706
- result.append(f"Line {i+1} does not match: Regex='{r}' String='{s}'")
707
- return result
708
-
709
- @GeneralUtilities.check_arguments
710
- def __standardized_tasks_build_for_dotnet_build(self, csproj_file: str, originaloutputfolder: str, files_to_sign: dict[str, str], commitid: str, verbosity: int, runtimes: list[str], target_environmenttype: str, target_environmenttype_mapping: dict[str, str], copy_license_file_to_target_folder: bool, repository_folder: str, codeunit_name: str, commandline_arguments: list[str]) -> None:
711
- self.__sc.assert_is_git_repository(repository_folder)
712
- csproj_filename = os.path.basename(csproj_file)
713
- GeneralUtilities.write_message_to_stdout(f"Build {csproj_filename}...")
714
- dotnet_build_configuration: str = target_environmenttype_mapping[target_environmenttype]
715
- verbosity = self.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
716
- codeunit_folder = os.path.join(repository_folder, codeunit_name)
717
- csproj_file_folder = os.path.dirname(csproj_file)
718
- csproj_file_name = os.path.basename(csproj_file)
719
- csproj_file_name_without_extension = csproj_file_name.split(".")[0]
720
- sarif_folder = os.path.join(codeunit_folder, "Other", "Resources", "CodeAnalysisResult")
721
- GeneralUtilities.ensure_directory_exists(sarif_folder)
722
- gitkeep_file = os.path.join(sarif_folder, ".gitkeep")
723
- GeneralUtilities.ensure_file_exists(gitkeep_file)
724
- for runtime in runtimes:
725
- outputfolder = originaloutputfolder+runtime
726
- GeneralUtilities.ensure_directory_does_not_exist(os.path.join(csproj_file_folder, "obj"))
727
- GeneralUtilities.ensure_directory_does_not_exist(outputfolder)
728
- self.__sc.run_program("dotnet", "clean", csproj_file_folder, verbosity=verbosity)
729
- GeneralUtilities.ensure_directory_exists(outputfolder)
730
- self.__sc.run_program("dotnet", "restore", codeunit_folder, verbosity=verbosity)
731
- self.__sc.run_program_argsasarray("dotnet", ["build", csproj_file_name, "-c", dotnet_build_configuration, "-o", outputfolder, "--runtime", runtime], csproj_file_folder, verbosity=verbosity)
732
- if copy_license_file_to_target_folder:
733
- license_file = os.path.join(repository_folder, "License.txt")
734
- target = os.path.join(outputfolder, f"{codeunit_name}.License.txt")
735
- shutil.copyfile(license_file, target)
736
- if 0 < len(files_to_sign):
737
- for key, value in files_to_sign.items():
738
- dll_file = key
739
- snk_file = value
740
- dll_file_full = os.path.join(outputfolder, dll_file)
741
- if os.path.isfile(dll_file_full):
742
- GeneralUtilities.assert_condition(self.__sc.run_program("sn", f"-vf {dll_file}", outputfolder, throw_exception_if_exitcode_is_not_zero=False)[0] == 1, f"Pre-verifying of {dll_file} failed.")
743
- self.__sc.run_program_argsasarray("sn", ["-R", dll_file, snk_file], outputfolder)
744
- GeneralUtilities.assert_condition(self.__sc.run_program("sn", f"-vf {dll_file}", outputfolder, throw_exception_if_exitcode_is_not_zero=False)[0] == 0, f"Verifying of {dll_file} failed.")
745
- sarif_filename = f"{csproj_file_name_without_extension}.sarif"
746
- sarif_source_file = os.path.join(sarif_folder, sarif_filename)
747
- if os.path.exists(sarif_source_file):
748
- sarif_folder_target = os.path.join(codeunit_folder, "Other", "Artifacts", "CodeAnalysisResult")
749
- GeneralUtilities.ensure_directory_exists(sarif_folder_target)
750
- sarif_target_file = os.path.join(sarif_folder_target, sarif_filename)
751
- GeneralUtilities.ensure_file_does_not_exist(sarif_target_file)
752
- shutil.copyfile(sarif_source_file, sarif_target_file)
753
-
754
- @GeneralUtilities.check_arguments
755
- def standardized_tasks_build_for_dotnet_project(self, buildscript_file: str, default_target_environmenttype: str, target_environmenttype_mapping: dict[str, str], runtimes: list[str], verbosity: int, commandline_arguments: list[str]) -> None:
756
- # hint: arguments can be overwritten by commandline_arguments
757
- # this function builds an exe
758
- target_environmenttype = self.get_targetenvironmenttype_from_commandline_arguments(commandline_arguments, default_target_environmenttype)
759
- self.__standardized_tasks_build_for_dotnet_project(
760
- buildscript_file, target_environmenttype_mapping, default_target_environmenttype, verbosity, target_environmenttype, runtimes, True, commandline_arguments)
761
-
762
- @GeneralUtilities.check_arguments
763
- def standardized_tasks_build_for_dotnet_library_project(self, buildscript_file: str, default_target_environmenttype: str, target_environmenttype_mapping: dict[str, str], runtimes: list[str], verbosity: int, commandline_arguments: list[str]) -> None:
764
- # hint: arguments can be overwritten by commandline_arguments
765
- # this function builds a dll and converts it to a nupkg-file
766
-
767
- target_environmenttype = self.get_targetenvironmenttype_from_commandline_arguments(commandline_arguments, default_target_environmenttype)
768
- self.__standardized_tasks_build_for_dotnet_project(buildscript_file, target_environmenttype_mapping, default_target_environmenttype, verbosity, target_environmenttype, runtimes, True, commandline_arguments)
769
- self.__standardized_tasks_build_nupkg_for_dotnet_create_package(buildscript_file, verbosity, commandline_arguments)
770
-
771
- @GeneralUtilities.check_arguments
772
- def get_default_target_environmenttype_mapping(self) -> dict[str, str]:
773
- return {
774
- TasksForCommonProjectStructure.get_development_environment_name(): TasksForCommonProjectStructure.get_development_environment_name(),
775
- TasksForCommonProjectStructure.get_qualitycheck_environment_name(): TasksForCommonProjectStructure.get_qualitycheck_environment_name(),
776
- TasksForCommonProjectStructure.get_productive_environment_name(): TasksForCommonProjectStructure.get_productive_environment_name()
777
- }
778
-
779
- @GeneralUtilities.check_arguments
780
- def __standardized_tasks_build_for_dotnet_project(self, buildscript_file: str, target_environmenttype_mapping: dict[str, str], default_target_environment_type: str, verbosity: int, target_environment_type: str, runtimes: list[str], copy_license_file_to_target_folder: bool, commandline_arguments: list[str]) -> None:
781
- codeunitname: str = os.path.basename(str(Path(os.path.dirname(buildscript_file)).parent.parent.absolute()))
782
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
783
- files_to_sign: dict[str, str] = TasksForCommonProjectStructure.get_filestosign_from_commandline_arguments(commandline_arguments, dict())
784
- repository_folder: str = str(Path(os.path.dirname(buildscript_file)).parent.parent.parent.absolute())
785
- commitid = self.__sc.git_get_commit_id(repository_folder)
786
- outputfolder = GeneralUtilities.resolve_relative_path("../Artifacts", os.path.dirname(buildscript_file))
787
- codeunit_folder = os.path.join(repository_folder, codeunitname)
788
- csproj_file = os.path.join(codeunit_folder, codeunitname, codeunitname + ".csproj")
789
- csproj_test_file = os.path.join(codeunit_folder, codeunitname+"Tests", codeunitname+"Tests.csproj")
790
- self.__standardized_tasks_build_for_dotnet_build(csproj_file, os.path.join(outputfolder, "BuildResult_DotNet_"), files_to_sign, commitid, verbosity, runtimes, target_environment_type, target_environmenttype_mapping, copy_license_file_to_target_folder, repository_folder, codeunitname, commandline_arguments)
791
- self.__standardized_tasks_build_for_dotnet_build(csproj_test_file, os.path.join(outputfolder, "BuildResultTests_DotNet_"), files_to_sign, commitid, verbosity, runtimes, target_environment_type, target_environmenttype_mapping, copy_license_file_to_target_folder, repository_folder, codeunitname, commandline_arguments)
792
- self.generate_sbom_for_dotnet_project(codeunit_folder, verbosity, commandline_arguments)
793
- self.copy_source_files_to_output_directory(buildscript_file)
794
-
795
- @GeneralUtilities.check_arguments
796
- def __standardized_tasks_build_nupkg_for_dotnet_create_package(self, buildscript_file: str, verbosity: int, commandline_arguments: list[str]) -> None:
797
- codeunitname: str = os.path.basename(str(Path(os.path.dirname(buildscript_file)).parent.parent.absolute()))
798
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
799
- repository_folder: str = str(Path(os.path.dirname(buildscript_file)).parent.parent.parent.absolute())
800
- build_folder = os.path.join(repository_folder, codeunitname, "Other", "Build")
801
- outputfolder = GeneralUtilities.resolve_relative_path("../Artifacts/BuildResult_NuGet", os.path.dirname(buildscript_file))
802
- root: etree._ElementTree = etree.parse(os.path.join(build_folder, f"{codeunitname}.nuspec"))
803
- current_version = root.xpath("//*[name() = 'package']/*[name() = 'metadata']/*[name() = 'version']/text()")[0]
804
- nupkg_filename = f"{codeunitname}.{current_version}.nupkg"
805
- nupkg_file = f"{build_folder}/{nupkg_filename}"
806
- GeneralUtilities.ensure_file_does_not_exist(nupkg_file)
807
- commit_id = self.__sc.git_get_commit_id(repository_folder)
808
- self.__sc.run_program("nuget", f"pack {codeunitname}.nuspec -Properties \"commitid={commit_id}\"", build_folder, verbosity=verbosity)
809
- GeneralUtilities.ensure_directory_does_not_exist(outputfolder)
810
- GeneralUtilities.ensure_directory_exists(outputfolder)
811
- os.rename(nupkg_file, f"{outputfolder}/{nupkg_filename}")
812
-
813
- @GeneralUtilities.check_arguments
814
- def generate_sbom_for_dotnet_project(self, codeunit_folder: str, verbosity: int, commandline_arguments: list[str]) -> None:
815
- GeneralUtilities.write_message_to_stdout("Generate SBOM...")
816
- self.assert_is_codeunit_folder(codeunit_folder)
817
- codeunit_name = os.path.basename(codeunit_folder)
818
- bomfile_folder = "Other\\Artifacts\\BOM"
819
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
820
- self.__sc.run_program_argsasarray("dotnet", ["CycloneDX", f"{codeunit_name}\\{codeunit_name}.csproj", "-o", bomfile_folder, "--disable-github-licenses"], codeunit_folder, verbosity=verbosity)
821
- codeunitversion = self.get_version_of_codeunit(os.path.join(codeunit_folder, f"{codeunit_name}.codeunit.xml"))
822
- target = f"{codeunit_folder}\\{bomfile_folder}\\{codeunit_name}.{codeunitversion}.sbom.xml"
823
- GeneralUtilities.ensure_file_does_not_exist(target)
824
- os.rename(f"{codeunit_folder}\\{bomfile_folder}\\bom.xml", target)
825
- self.__sc.format_xml_file(target)
826
-
827
- @GeneralUtilities.check_arguments
828
- def standardized_tasks_run_linting_for_flutter_project_in_common_project_structure(self, script_file: str, default_verbosity: int, args: list[str]):
829
- pass # TODO
830
-
831
- @GeneralUtilities.check_arguments
832
- def standardized_tasks_linting_for_python_codeunit(self, linting_script_file: str, verbosity: int, targetenvironmenttype: str, commandline_arguments: list[str]) -> None:
833
- codeunitname: str = Path(os.path.dirname(linting_script_file)).parent.parent.name
834
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
835
- repository_folder: str = str(Path(os.path.dirname(linting_script_file)).parent.parent.parent.absolute())
836
- errors_found = False
837
- GeneralUtilities.write_message_to_stdout(f"Check for linting-issues in codeunit {codeunitname}.")
838
- src_folder = os.path.join(repository_folder, codeunitname, codeunitname)
839
- tests_folder = src_folder+"Tests"
840
- # TODO check if there are errors in sarif-file
841
- for file in GeneralUtilities.get_all_files_of_folder(src_folder)+GeneralUtilities.get_all_files_of_folder(tests_folder):
842
- relative_file_path_in_repository = os.path.relpath(file, repository_folder)
843
- if file.endswith(".py") and os.path.getsize(file) > 0 and not self.__sc.file_is_git_ignored(relative_file_path_in_repository, repository_folder):
844
- GeneralUtilities.write_message_to_stdout(f"Check for linting-issues in {os.path.relpath(file, os.path.join(repository_folder, codeunitname))}.")
845
- linting_result = self.__sc.python_file_has_errors(file, repository_folder)
846
- if (linting_result[0]):
847
- errors_found = True
848
- for error in linting_result[1]:
849
- GeneralUtilities.write_message_to_stderr(error)
850
- if errors_found:
851
- raise ValueError("Linting-issues occurred.")
852
- else:
853
- GeneralUtilities.write_message_to_stdout("No linting-issues found.")
854
-
855
- @GeneralUtilities.check_arguments
856
- def standardized_tasks_generate_coverage_report(self, repository_folder: str, codeunitname: str, verbosity: int, generate_badges: bool, targetenvironmenttype: str, commandline_arguments: list[str], add_testcoverage_history_entry: bool = None) -> None:
857
- """This function expects that the file '<repositorybasefolder>/<codeunitname>/Other/Artifacts/TestCoverage/TestCoverage.xml'
858
- which contains a test-coverage-report in the cobertura-format exists.
859
- This script expectes that the testcoverage-reportfolder is '<repositorybasefolder>/<codeunitname>/Other/Artifacts/TestCoverageReport'.
860
- This script expectes that a test-coverage-badges should be added to '<repositorybasefolder>/<codeunitname>/Other/Resources/Badges'."""
861
- GeneralUtilities.write_message_to_stdout("Generate testcoverage report..")
862
- self.__sc.assert_is_git_repository(repository_folder)
863
- codeunit_version = self.get_version_of_codeunit(os.path.join(repository_folder, codeunitname, f"{codeunitname}.codeunit.xml"))
864
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
865
- if verbosity == 0:
866
- verbose_argument_for_reportgenerator = "Off"
867
- elif verbosity == 1:
868
- verbose_argument_for_reportgenerator = "Error"
869
- elif verbosity == 2:
870
- verbose_argument_for_reportgenerator = "Info"
871
- elif verbosity == 3:
872
- verbose_argument_for_reportgenerator = "Verbose"
873
- else:
874
- raise ValueError(f"Unknown value for verbosity: {GeneralUtilities.str_none_safe(verbosity)}")
875
-
876
- # Generating report
877
- GeneralUtilities.ensure_directory_does_not_exist(os.path.join(repository_folder, codeunitname, f"{codeunitname}/Other/Artifacts/TestCoverageReport"))
878
- GeneralUtilities.ensure_directory_exists(os.path.join(repository_folder, codeunitname, "Other/Artifacts/TestCoverageReport"))
879
-
880
- if add_testcoverage_history_entry is None:
881
- add_testcoverage_history_entry = self.get_is_pre_merge_value_from_commandline_arguments(commandline_arguments, add_testcoverage_history_entry)
882
-
883
- history_folder = f"{codeunitname}/Other/Resources/TestCoverageHistory"
884
- history_folder_full = os.path.join(repository_folder, history_folder)
885
- GeneralUtilities.ensure_directory_exists(history_folder_full)
886
- history_argument = f" -historydir:{history_folder}"
887
- argument = f"-reports:{codeunitname}/Other/Artifacts/TestCoverage/TestCoverage.xml -targetdir:{codeunitname}/Other/Artifacts/TestCoverageReport --verbosity:{verbose_argument_for_reportgenerator}{history_argument} -title:{codeunitname} -tag:v{codeunit_version}"
888
- self.__sc.run_program("reportgenerator", argument, repository_folder, verbosity=verbosity)
889
- if not add_testcoverage_history_entry:
890
- os.remove(GeneralUtilities.get_direct_files_of_folder(history_folder_full)[-1])
891
-
892
- # Generating badges
893
- if generate_badges:
894
- testcoverageubfolger = "Other/Resources/TestCoverageBadges"
895
- fulltestcoverageubfolger = os.path.join(repository_folder, codeunitname, testcoverageubfolger)
896
- GeneralUtilities.ensure_directory_does_not_exist(fulltestcoverageubfolger)
897
- GeneralUtilities.ensure_directory_exists(fulltestcoverageubfolger)
898
- self.__sc.run_program("reportgenerator", f"-reports:Other/Artifacts/TestCoverage/TestCoverage.xml -targetdir:{testcoverageubfolger} -reporttypes:Badges --verbosity:{verbose_argument_for_reportgenerator}", os.path.join(repository_folder, codeunitname), verbosity=verbosity)
899
-
900
- @GeneralUtilities.check_arguments
901
- def standardized_tasks_run_testcases_for_dotnet_project(self, runtestcases_file: str, targetenvironmenttype: str, verbosity: int, generate_badges: bool, target_environmenttype_mapping: dict[str, str], commandline_arguments: list[str]) -> None:
902
- GeneralUtilities.write_message_to_stdout("Run testcases...")
903
- dotnet_build_configuration: str = target_environmenttype_mapping[targetenvironmenttype]
904
- codeunit_name: str = os.path.basename(str(Path(os.path.dirname(runtestcases_file)).parent.parent.absolute()))
905
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
906
- repository_folder: str = str(Path(os.path.dirname(runtestcases_file)).parent.parent.parent.absolute()).replace("\\", "/")
907
- coverage_file_folder = os.path.join(repository_folder, codeunit_name, "Other/Artifacts/TestCoverage")
908
- temp_folder = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
909
- GeneralUtilities.ensure_directory_exists(temp_folder)
910
- runsettings_file = self.dotnet_runsettings_file
911
- codeunit_folder = f"{repository_folder}/{codeunit_name}"
912
- arg = f"test . -c {dotnet_build_configuration} -o {temp_folder}"
913
- if os.path.isfile(os.path.join(codeunit_folder, runsettings_file)):
914
- arg = f"{arg} --settings {runsettings_file}"
915
- arg = f"{arg} /p:CollectCoverage=true /p:CoverletOutput=../Other/Artifacts/TestCoverage/Testcoverage /p:CoverletOutputFormat=cobertura"
916
- self.__sc.run_program("dotnet", arg, codeunit_folder, verbosity=verbosity, print_live_output=True)
917
- target_file = os.path.join(coverage_file_folder, "TestCoverage.xml")
918
- GeneralUtilities.ensure_file_does_not_exist(target_file)
919
- os.rename(os.path.join(coverage_file_folder, "Testcoverage.cobertura.xml"), target_file)
920
- self.__remove_unrelated_package_from_testcoverage_file(target_file, codeunit_name)
921
- root: etree._ElementTree = etree.parse(target_file)
922
- source_base_path_in_coverage_file: str = root.xpath("//coverage/sources/source/text()")[0].replace("\\", "/")
923
- content = GeneralUtilities.read_text_from_file(target_file)
924
- GeneralUtilities.assert_condition(source_base_path_in_coverage_file.startswith(repository_folder) or repository_folder.startswith(source_base_path_in_coverage_file), f"Unexpected path for coverage. Sourcepath: \"{source_base_path_in_coverage_file}\"; repository: \"{repository_folder}\"")
925
- content = re.sub('\\\\', '/', content)
926
- content = re.sub("filename=\"([^\"]+)\"", lambda match: self.__standardized_tasks_run_testcases_for_dotnet_project_helper(source_base_path_in_coverage_file, codeunit_folder, match), content)
927
- GeneralUtilities.write_text_to_file(target_file, content)
928
- self.run_testcases_common_post_task(repository_folder, codeunit_name, verbosity, generate_badges, targetenvironmenttype, commandline_arguments)
929
- artifacts_folder = os.path.join(repository_folder, codeunit_name, "Other", "Artifacts")
930
- for subfolder in GeneralUtilities.get_direct_folders_of_folder(artifacts_folder):
931
- if os.path.basename(subfolder).startswith("BuildResultTests_DotNet_"):
932
- GeneralUtilities.ensure_directory_does_not_exist(subfolder)
933
-
934
- @GeneralUtilities.check_arguments
935
- def run_testcases_common_post_task(self, repository_folder: str, codeunit_name: str, verbosity: int, generate_badges: bool, targetenvironmenttype: str, commandline_arguments: list[str]) -> None:
936
- self.__sc.assert_is_git_repository(repository_folder)
937
- coverage_file_folder = os.path.join(repository_folder, codeunit_name, "Other/Artifacts/TestCoverage")
938
- coveragefiletarget = os.path.join(coverage_file_folder, "TestCoverage.xml")
939
- self.update_path_of_source_in_testcoverage_file(repository_folder, codeunit_name)
940
- self.standardized_tasks_generate_coverage_report(repository_folder, codeunit_name, verbosity, generate_badges, targetenvironmenttype, commandline_arguments)
941
- self.check_testcoverage(coveragefiletarget, repository_folder, codeunit_name)
942
-
943
- @GeneralUtilities.check_arguments
944
- def update_path_of_source_in_testcoverage_file(self, repository_folder: str, codeunitname: str) -> None:
945
- self.__sc.assert_is_git_repository(repository_folder)
946
- GeneralUtilities.write_message_to_stdout("Update paths of source files in testcoverage files..")
947
- folder = f"{repository_folder}/{codeunitname}/Other/Artifacts/TestCoverage"
948
- filename = "TestCoverage.xml"
949
- full_file = os.path.join(folder, filename)
950
- GeneralUtilities.write_text_to_file(full_file, re.sub("<source>.+<\\/source>", f"<source><!--[repository]/-->./{codeunitname}/</source>", GeneralUtilities.read_text_from_file(full_file)))
951
- self.__remove_not_existing_files_from_testcoverage_file(full_file, repository_folder, codeunitname)
952
-
953
- @GeneralUtilities.check_arguments
954
- def __standardized_tasks_run_testcases_for_dotnet_project_helper(self, source: str, codeunit_folder: str, match: re.Match) -> str:
955
- self.assert_is_codeunit_folder(codeunit_folder)
956
- filename = match.group(1)
957
- file = os.path.join(source, filename)
958
- # GeneralUtilities.assert_condition(os.path.isfile(file),f"File \"{file}\" does not exist.")
959
- GeneralUtilities.assert_condition(file.startswith(codeunit_folder), f"Unexpected path for coverage-file. File: \"{file}\"; codeunitfolder: \"{codeunit_folder}\"")
960
- filename_relative = f".{file[len(codeunit_folder):]}"
961
- return f'filename="{filename_relative}"'
962
-
963
- @GeneralUtilities.check_arguments
964
- def __remove_not_existing_files_from_testcoverage_file(self, testcoveragefile: str, repository_folder: str, codeunit_name: str) -> None:
965
- self.__sc.assert_is_git_repository(repository_folder)
966
- root: etree._ElementTree = etree.parse(testcoveragefile)
967
- codeunit_folder = os.path.join(repository_folder, codeunit_name)
968
- xpath = f"//coverage/packages/package[@name='{codeunit_name}']/classes/class"
969
- coverage_report_classes = root.xpath(xpath)
970
- found_existing_files = False
971
- for coverage_report_class in coverage_report_classes:
972
- filename = coverage_report_class.attrib['filename']
973
- file = os.path.join(codeunit_folder, filename)
974
- if os.path.isfile(file):
975
- found_existing_files = True
976
- else:
977
- coverage_report_class.getparent().remove(coverage_report_class)
978
- GeneralUtilities.assert_condition(found_existing_files, f"No existing files in testcoderage-report-file \"{testcoveragefile}\".")
979
- result = etree.tostring(root).decode("utf-8")
980
- GeneralUtilities.write_text_to_file(testcoveragefile, result)
981
-
982
- @GeneralUtilities.check_arguments
983
- def __remove_unrelated_package_from_testcoverage_file(self, file: str, codeunit_name: str) -> None:
984
- root: etree._ElementTree = etree.parse(file)
985
- packages = root.xpath('//coverage/packages/package')
986
- for package in packages:
987
- if package.attrib['name'] != codeunit_name:
988
- package.getparent().remove(package)
989
- result = etree.tostring(root).decode("utf-8")
990
- GeneralUtilities.write_text_to_file(file, result)
991
-
992
- @GeneralUtilities.check_arguments
993
- def write_version_to_codeunit_file(self, codeunit_file: str, current_version: str) -> None:
994
- versionregex = "\\d+\\.\\d+\\.\\d+"
995
- versiononlyregex = f"^{versionregex}$"
996
- pattern = re.compile(versiononlyregex)
997
- if pattern.match(current_version):
998
- GeneralUtilities.write_text_to_file(codeunit_file, re.sub(f"<cps:version>{versionregex}<\\/cps:version>", f"<cps:version>{current_version}</cps:version>", GeneralUtilities.read_text_from_file(codeunit_file)))
999
- else:
1000
- raise ValueError(f"Version '{current_version}' does not match version-regex '{versiononlyregex}'.")
1001
-
1002
- @GeneralUtilities.check_arguments
1003
- def standardized_tasks_linting_for_dotnet_project(self, linting_script_file: str, verbosity: int, targetenvironmenttype: str, commandline_arguments: list[str]) -> None:
1004
- GeneralUtilities.write_message_to_stdout("Run linting...")
1005
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1006
- # TODO check if there are errors in sarif-file
1007
-
1008
- @GeneralUtilities.check_arguments
1009
- def __export_codeunit_reference_content_to_reference_repository(self, project_version_identifier: str, replace_existing_content: bool, target_folder_for_reference_repository: str, repository: str, codeunitname: str, projectname: str, codeunit_version: str, public_repository_url: str, branch: str) -> None:
1010
- codeunit_folder = os.path.join(repository, codeunitname)
1011
- codeunit_file = os.path.join(codeunit_folder, f"{codeunitname}.codeunit.xml")
1012
- codeunit_has_testcases = self.codeunit_has_testable_sourcecode(codeunit_file)
1013
- target_folder = os.path.join(target_folder_for_reference_repository, project_version_identifier, codeunitname)
1014
- if os.path.isdir(target_folder) and not replace_existing_content:
1015
- raise ValueError(f"Folder '{target_folder}' already exists.")
1016
- GeneralUtilities.ensure_directory_does_not_exist(target_folder)
1017
- GeneralUtilities.ensure_directory_exists(target_folder)
1018
- codeunit_version_identifier = "Latest" if project_version_identifier == "Latest" else "v"+codeunit_version
1019
- page_title = f"{codeunitname} {codeunit_version_identifier} codeunit-reference"
1020
- diff_report = f"{repository}/{codeunitname}/Other/Artifacts/DiffReport/DiffReport.html"
1021
- diff_target_folder = os.path.join(target_folder, "DiffReport")
1022
- GeneralUtilities.ensure_directory_exists(diff_target_folder)
1023
- diff_target_file = os.path.join(diff_target_folder, "DiffReport.html")
1024
- title = (f'Reference of codeunit {codeunitname} {codeunit_version_identifier} (contained in project <a href="{public_repository_url}">{projectname}</a> {project_version_identifier})')
1025
- if public_repository_url is None:
1026
- repo_url_html = GeneralUtilities.empty_string
1027
- else:
1028
- repo_url_html = f'<a href="{public_repository_url}/tree/{branch}/{codeunitname}">Source-code</a>'
1029
- if codeunit_has_testcases:
1030
- coverage_report_link = '<a href="./TestCoverageReport/index.html">Test-coverage-report</a><br>'
1031
- else:
1032
- coverage_report_link = GeneralUtilities.empty_string
1033
- index_file_for_reference = os.path.join(target_folder, "index.html")
1034
-
1035
- design_file = None
1036
- design = "ModestDark"
1037
- if design == "ModestDark":
1038
- design_file = GeneralUtilities.get_modest_dark_url()
1039
- # TODO make designs from customizable sources be available by a customizable name and outsource this to a class-property because this is duplicated code.
1040
- if design_file is None:
1041
- design_html = GeneralUtilities.empty_string
1042
- else:
1043
- design_html = f'<link type="text/css" rel="stylesheet" href="{design_file}" />'
1044
-
1045
- index_file_content = f"""<!DOCTYPE html>
1046
- <html lang="en">
1047
-
1048
- <head>
1049
- <meta charset="UTF-8">
1050
- <title>{page_title}</title>
1051
- {design_html}
1052
- </head>
1053
-
1054
- <body>
1055
- <h1>{title}</h1>
1056
- <hr/>
1057
- Available reference-content for {codeunitname}:<br>
1058
- {repo_url_html}<br>
1059
- <!--TODO add artefacts-link: <a href="./x">Artefacts</a><br>-->
1060
- <a href="./Reference/index.html">Reference</a><br>
1061
- <a href="./DiffReport/DiffReport.html">Diff-report</a><br>
1062
- {coverage_report_link}
1063
- </body>
1064
-
1065
- </html>
1066
- """
1067
-
1068
- GeneralUtilities.ensure_file_exists(index_file_for_reference)
1069
- GeneralUtilities.write_text_to_file(index_file_for_reference, index_file_content)
1070
- other_folder_in_repository = os.path.join(repository, codeunitname, "Other")
1071
- source_generatedreference = os.path.join(other_folder_in_repository, "Artifacts", "Reference")
1072
- target_generatedreference = os.path.join(target_folder, "Reference")
1073
- shutil.copytree(source_generatedreference, target_generatedreference)
1074
-
1075
- shutil.copyfile(diff_report, diff_target_file)
1076
-
1077
- if codeunit_has_testcases:
1078
- source_testcoveragereport = os.path.join(other_folder_in_repository, "Artifacts", "TestCoverageReport")
1079
- if os.path.isdir(source_testcoveragereport): # check, because it is not a mandatory artifact. if the artifact is not available, the user gets already a warning.
1080
- target_testcoveragereport = os.path.join(target_folder, "TestCoverageReport")
1081
- shutil.copytree(source_testcoveragereport, target_testcoveragereport)
1082
-
1083
- @GeneralUtilities.check_arguments
1084
- def __standardized_tasks_release_artifact(self, information: CreateReleaseInformationForProjectInCommonProjectFormat) -> None:
1085
- GeneralUtilities.write_message_to_stdout("Release artifacts...")
1086
- project_version = self.__sc.get_semver_version_from_gitversion(information.repository)
1087
- target_folder_base = os.path.join(information.artifacts_folder, information.projectname, project_version)
1088
- GeneralUtilities.ensure_directory_exists(target_folder_base)
1089
-
1090
- self.build_codeunits(information.repository, information.verbosity, information.target_environmenttype_for_productive, information.additional_arguments_file, False, information.export_target, [], True, "Generate artifacts") # Generate artifacts after merge (because now are constants like commit-id of the new version available)
1091
-
1092
- reference_folder = os.path.join(information.reference_repository, "ReferenceContent")
1093
-
1094
- for codeunitname in self.get_codeunits(information.repository):
1095
- # Push artifacts to registry
1096
- if information.verbosity > 2:
1097
- GeneralUtilities.write_message_to_stdout(f"Push artifacts of {codeunitname}...")
1098
- scriptfilename = f"PushArtifacts.{codeunitname}.py"
1099
- push_artifact_to_registry_script = os.path.join(information.push_artifacts_scripts_folder, scriptfilename)
1100
- if os.path.isfile(push_artifact_to_registry_script):
1101
- GeneralUtilities.write_message_to_stdout(f"Push artifacts of codeunit {codeunitname}...")
1102
- self.__sc.run_program("python", push_artifact_to_registry_script, information.push_artifacts_scripts_folder, verbosity=information.verbosity, throw_exception_if_exitcode_is_not_zero=True)
1103
-
1104
- # Copy reference of codeunit to reference-repository
1105
- codeunit_version = self.get_version_of_codeunit_folder(os.path.join(information.repository, codeunitname))
1106
- self.__export_codeunit_reference_content_to_reference_repository(f"v{project_version}", False, reference_folder, information.repository, codeunitname, information.projectname, codeunit_version, information.public_repository_url, f"v{project_version}")
1107
- self.__export_codeunit_reference_content_to_reference_repository("Latest", True, reference_folder, information.repository, codeunitname, information.projectname, codeunit_version, information.public_repository_url, information.target_branch_name)
1108
-
1109
- # Generate reference
1110
- self.__generate_entire_reference(information.projectname, project_version, reference_folder)
1111
-
1112
- @staticmethod
1113
- @GeneralUtilities.check_arguments
1114
- def _internal_sort_reference_folder(folder1: str, folder2: str) -> int:
1115
- """Returns a value greater than 0 if and only if folder1 has a base-folder-name with a with a higher version than the base-folder-name of folder2.
1116
- Returns a value lower than 0 if and only if folder1 has a base-folder-name with a with a lower version than the base-folder-name of folder2.
1117
- Returns 0 if both values are equal."""
1118
- if (folder1 == folder2):
1119
- return 0
1120
-
1121
- version_identifier_1 = os.path.basename(folder1)
1122
- if version_identifier_1 == "Latest":
1123
- return -1
1124
- version_identifier_1 = version_identifier_1[1:]
1125
-
1126
- version_identifier_2 = os.path.basename(folder2)
1127
- if version_identifier_2 == "Latest":
1128
- return 1
1129
- version_identifier_2 = version_identifier_2[1:]
1130
-
1131
- if version.parse(version_identifier_1) < version.parse(version_identifier_2):
1132
- return -1
1133
- elif version.parse(version_identifier_1) > version.parse(version_identifier_2):
1134
- return 1
1135
- else:
1136
- return 0
1137
-
1138
- @GeneralUtilities.check_arguments
1139
- def __generate_entire_reference(self, projectname: str, project_version: str, reference_folder: str) -> None:
1140
- all_available_version_identifier_folders_of_reference: list[str] = list(folder for folder in GeneralUtilities.get_direct_folders_of_folder(reference_folder))
1141
- all_available_version_identifier_folders_of_reference = sorted(all_available_version_identifier_folders_of_reference, key=cmp_to_key(TasksForCommonProjectStructure._internal_sort_reference_folder))
1142
- reference_versions_html_lines = []
1143
- reference_versions_html_lines.append(' <hr/>')
1144
- for all_available_version_identifier_folder_of_reference in all_available_version_identifier_folders_of_reference:
1145
- version_identifier_of_project = os.path.basename(all_available_version_identifier_folder_of_reference)
1146
- if version_identifier_of_project == "Latest":
1147
- latest_version_hint = f" (v{project_version})"
1148
- else:
1149
- latest_version_hint = GeneralUtilities.empty_string
1150
- reference_versions_html_lines.append(f' <h2>{version_identifier_of_project}{latest_version_hint}</h2>')
1151
- reference_versions_html_lines.append(" Contained codeunits:<br/>")
1152
- reference_versions_html_lines.append(" <ul>")
1153
- for codeunit_reference_folder in list(folder for folder in GeneralUtilities.get_direct_folders_of_folder(all_available_version_identifier_folder_of_reference)):
1154
- reference_versions_html_lines.append(f' <li><a href="./{version_identifier_of_project}/{os.path.basename(codeunit_reference_folder)}/index.html">' +
1155
- f'{os.path.basename(codeunit_reference_folder)} {version_identifier_of_project}</a></li>')
1156
- reference_versions_html_lines.append(" </ul>")
1157
- reference_versions_html_lines.append(' <hr/>')
1158
- if version_identifier_of_project == "Latest":
1159
- latest_version_hint = " <h2>History</h2>"
1160
-
1161
- design_file = None
1162
- design = "ModestDark"
1163
- if design == "ModestDark":
1164
- design_file = GeneralUtilities.get_modest_dark_url()
1165
- # TODO make designs from customizable sources be available by a customizable name and outsource this to a class-property because this is duplicated code.
1166
- if design_file is None:
1167
- design_html = GeneralUtilities.empty_string
1168
- else:
1169
- design_html = f'<link type="text/css" rel="stylesheet" href="{design_file}" />'
1170
-
1171
- reference_versions_links_file_content = " \n".join(reference_versions_html_lines)
1172
- title = f"{projectname}-reference"
1173
- reference_index_file = os.path.join(reference_folder, "index.html")
1174
- reference_index_file_content = f"""<!DOCTYPE html>
1175
- <html lang="en">
1176
-
1177
- <head>
1178
- <meta charset="UTF-8">
1179
- <title>{title}</title>
1180
- {design_html}
1181
- </head>
1182
-
1183
- <body>
1184
- <h1>{title}</h1>
1185
- {reference_versions_links_file_content}
1186
- </body>
1187
-
1188
- </html>
1189
- """ # see https://getbootstrap.com/docs/5.1/getting-started/introduction/
1190
- GeneralUtilities.write_text_to_file(reference_index_file, reference_index_file_content)
1191
-
1192
- @GeneralUtilities.check_arguments
1193
- def push_nuget_build_artifact(self, push_script_file: str, codeunitname: str, registry_address: str, repository_folder_name: str, api_key: str):
1194
- # when pusing to "default public" nuget-server then use registry_address: "nuget.org"
1195
- build_artifact_folder = GeneralUtilities.resolve_relative_path(f"../../Submodules/{repository_folder_name}/{codeunitname}/Other/Artifacts/BuildResult_NuGet", os.path.dirname(push_script_file))
1196
- self.__sc.push_nuget_build_artifact(self.__sc.find_file_by_extension(build_artifact_folder, "nupkg"), registry_address, api_key)
1197
-
1198
- @GeneralUtilities.check_arguments
1199
- def assert_no_uncommitted_changes(self, repository_folder: str):
1200
- if self.__sc.git_repository_has_uncommitted_changes(repository_folder):
1201
- raise ValueError(f"Repository '{repository_folder}' has uncommitted changes.")
1202
-
1203
- @GeneralUtilities.check_arguments
1204
- def ensure_certificate_authority_for_development_purposes_is_generated(self, product_folder: str):
1205
- product_name: str = os.path.basename(product_folder)
1206
- now = GeneralUtilities.get_now()
1207
- ca_name = f"{product_name}CA_{now.year:04}{now.month:02}{now.day:02}{now.hour:02}{now.min:02}{now.second:02}"
1208
- ca_folder = os.path.join(product_folder, "Other", "Resources", "CA")
1209
- generate_certificate = True
1210
- if os.path.isdir(ca_folder):
1211
- ca_files = [file for file in GeneralUtilities.get_direct_files_of_folder(ca_folder) if file.endswith(".crt")]
1212
- if len(ca_files) > 0:
1213
- ca_file = ca_files[-1] # pylint:disable=unused-variable
1214
- certificate_is_valid = True # TODO check if certificate is really valid
1215
- generate_certificate = not certificate_is_valid
1216
- if generate_certificate:
1217
- self.__sc.generate_certificate_authority(ca_folder, ca_name, "DE", "SubjST", "SubjL", "SubjO", "SubjOU")
1218
- # TODO add switch to auto-install the script if desired
1219
- # for windows: powershell Import-Certificate -FilePath ConSurvCA_20241121000236.crt -CertStoreLocation 'Cert:\CurrentUser\Root'
1220
- # for linux: (TODO)
1221
-
1222
- @GeneralUtilities.check_arguments
1223
- def generate_certificate_for_development_purposes_for_product(self, repository_folder: str):
1224
- self.__sc.assert_is_git_repository(repository_folder)
1225
- product_name = os.path.basename(repository_folder)
1226
- ca_folder: str = os.path.join(repository_folder, "Other", "Resources", "CA")
1227
- self.__generate_certificate_for_development_purposes(product_name, os.path.join(repository_folder, "Other", "Resources"), ca_folder, None)
1228
-
1229
- @GeneralUtilities.check_arguments
1230
- def generate_certificate_for_development_purposes_for_external_service(self, service_folder: str, domain: str = None):
1231
- testservice_name = os.path.basename(service_folder)
1232
- ca_folder: str = None # TODO
1233
- self.__generate_certificate_for_development_purposes(testservice_name, os.path.join(service_folder, "Resources"), ca_folder, domain)
1234
-
1235
- @GeneralUtilities.check_arguments
1236
- def generate_certificate_for_development_purposes_for_codeunit(self, codeunit_folder: str, domain: str = None):
1237
- self.assert_is_codeunit_folder(codeunit_folder)
1238
- codeunit_name = os.path.basename(codeunit_folder)
1239
- self.ensure_product_resource_is_imported(codeunit_folder, "CA")
1240
- ca_folder: str = os.path.join(codeunit_folder, "Other", "Resources", "CA")
1241
- self.__generate_certificate_for_development_purposes(codeunit_name, os.path.join(codeunit_folder, "Other", "Resources"), ca_folder, domain)
1242
-
1243
- @GeneralUtilities.check_arguments
1244
- def __generate_certificate_for_development_purposes(self, service_name: str, resources_folder: str, ca_folder: str, domain: str = None):
1245
- if domain is None:
1246
- domain = f"{service_name}.test.local"
1247
- domain = domain.lower()
1248
- resource_name: str = "DevelopmentCertificate"
1249
- certificate_folder: str = os.path.join(resources_folder, resource_name)
1250
-
1251
- resource_content_filename: str = service_name+resource_name
1252
- certificate_file = os.path.join(certificate_folder, f"{domain}.crt")
1253
- unsignedcertificate_file = os.path.join(certificate_folder, f"{domain}.unsigned.crt")
1254
- certificate_exists = os.path.exists(certificate_file)
1255
- if certificate_exists:
1256
- certificate_expired = GeneralUtilities.certificate_is_expired(certificate_file)
1257
- generate_new_certificate = certificate_expired
1258
- else:
1259
- generate_new_certificate = True
1260
- if generate_new_certificate:
1261
- GeneralUtilities.ensure_directory_does_not_exist(certificate_folder)
1262
- GeneralUtilities.ensure_directory_exists(certificate_folder)
1263
- GeneralUtilities.write_message_to_stdout("Generate TLS-certificate for development-purposes.")
1264
- self.__sc.generate_certificate(certificate_folder, domain, resource_content_filename, "DE", "SubjST", "SubjL", "SubjO", "SubjOU")
1265
- self.__sc.generate_certificate_sign_request(certificate_folder, domain, resource_content_filename, "DE", "SubjST", "SubjL", "SubjO", "SubjOU")
1266
- ca_name = os.path.basename(self.__sc.find_last_file_by_extension(ca_folder, "crt"))[:-4]
1267
- self.__sc.sign_certificate(certificate_folder, ca_folder, ca_name, domain, resource_content_filename)
1268
- GeneralUtilities.ensure_file_does_not_exist(unsignedcertificate_file)
1269
-
1270
- @GeneralUtilities.check_arguments
1271
- def copy_product_resource_to_codeunit_resource_folder(self, codeunit_folder: str, resourcename: str) -> None:
1272
- repository_folder = GeneralUtilities.resolve_relative_path(f"..", codeunit_folder)
1273
- self.__sc.assert_is_git_repository(repository_folder)
1274
- src_folder = GeneralUtilities.resolve_relative_path(f"Other/Resources/{resourcename}", repository_folder)
1275
- GeneralUtilities.assert_condition(os.path.isdir(src_folder), f"Required product-resource {resourcename} does not exist. Expected folder: {src_folder}")
1276
- trg_folder = GeneralUtilities.resolve_relative_path(f"Other/Resources/{resourcename}", codeunit_folder)
1277
- GeneralUtilities.ensure_directory_does_not_exist(trg_folder)
1278
- GeneralUtilities.ensure_directory_exists(trg_folder)
1279
- GeneralUtilities.copy_content_of_folder(src_folder, trg_folder)
1280
-
1281
- @GeneralUtilities.check_arguments
1282
- def ensure_product_resource_is_imported(self, codeunit_folder: str, product_resource_name: str) -> None:
1283
- product_folder = os.path.dirname(codeunit_folder)
1284
- source_folder = os.path.join(product_folder, "Other", "Resources", product_resource_name)
1285
- target_folder = os.path.join(codeunit_folder, "Other", "Resources", product_resource_name)
1286
- GeneralUtilities.ensure_directory_does_not_exist(target_folder)
1287
- GeneralUtilities.ensure_directory_exists(target_folder)
1288
- GeneralUtilities.copy_content_of_folder(source_folder, target_folder)
1289
-
1290
- @GeneralUtilities.check_arguments
1291
- def get_codeunits(self, repository_folder: str, ignore_disabled_codeunits: bool = True) -> list[str]:
1292
- codeunits_with_dependent_codeunits: dict[str, set[str]] = dict[str, set[str]]()
1293
- subfolders = GeneralUtilities.get_direct_folders_of_folder(repository_folder)
1294
- for subfolder in subfolders:
1295
- codeunit_name: str = os.path.basename(subfolder)
1296
- codeunit_file = os.path.join(subfolder, f"{codeunit_name}.codeunit.xml")
1297
- if os.path.exists(codeunit_file):
1298
- if ignore_disabled_codeunits and not self.codeunit_is_enabled(codeunit_file):
1299
- continue
1300
- codeunits_with_dependent_codeunits[codeunit_name] = self.get_dependent_code_units(codeunit_file)
1301
- sorted_codeunits = self._internal_get_sorted_codeunits_by_dict(codeunits_with_dependent_codeunits)
1302
- return sorted_codeunits
1303
-
1304
- @GeneralUtilities.check_arguments
1305
- def codeunit_is_enabled(self, codeunit_file: str) -> bool:
1306
- root: etree._ElementTree = etree.parse(codeunit_file)
1307
- return GeneralUtilities.string_to_boolean(str(root.xpath('//cps:codeunit/@enabled', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'})[0]))
1308
-
1309
- @GeneralUtilities.check_arguments
1310
- def merge_to_main_branch(self, repository_folder: str, source_branch: str = "other/next-release", target_branch: str = "main", verbosity: int = 1, additional_arguments_file: str = None, fast_forward_source_branch: bool = False) -> None:
1311
- # This is an automatization for automatic merges. Usual this merge would be done by a pull request in a sourcecode-version-control-platform
1312
- # (like GitHub, GitLab or Azure DevOps)
1313
- GeneralUtilities.write_message_to_stdout(f"Merge to main-branch...")
1314
- self.__sc.assert_is_git_repository(repository_folder)
1315
- self.assert_no_uncommitted_changes(repository_folder)
1316
-
1317
- src_branch_commit_id = self.__sc.git_get_commit_id(repository_folder, source_branch)
1318
- if (src_branch_commit_id == self.__sc.git_get_commit_id(repository_folder, target_branch)):
1319
- raise ValueError(f"Can not merge because the source-branch and the target-branch are on the same commit (commit-id: {src_branch_commit_id})")
1320
-
1321
- self.__sc.git_checkout(repository_folder, source_branch)
1322
- self.build_codeunits(repository_folder, verbosity, TasksForCommonProjectStructure.get_qualitycheck_environment_name(), additional_arguments_file, True, None, [], True, "Check if product is buildable")
1323
- self.__sc.git_merge(repository_folder, source_branch, target_branch, False, False, None, False, False)
1324
- self.__sc.git_commit(repository_folder, f'Merge branch {source_branch} into {target_branch}', stage_all_changes=True, no_changes_behavior=1)
1325
- self.__sc.git_checkout(repository_folder, target_branch)
1326
- if fast_forward_source_branch:
1327
- self.__sc.git_merge(repository_folder, target_branch, source_branch, True, True)
1328
-
1329
- @GeneralUtilities.check_arguments
1330
- def merge_to_stable_branch(self, create_release_file: str, createRelease_configuration: CreateReleaseConfiguration):
1331
-
1332
- GeneralUtilities.write_message_to_stdout(f"Create release for project {createRelease_configuration.projectname}.")
1333
- GeneralUtilities.write_message_to_stdout(f"Merge to stable-branch...")
1334
- self.__sc.assert_is_git_repository(createRelease_configuration.repository_folder)
1335
- folder_of_create_release_file_file = os.path.abspath(os.path.dirname(create_release_file))
1336
-
1337
- build_repository_folder = GeneralUtilities.resolve_relative_path(f"..{os.path.sep}..", folder_of_create_release_file_file)
1338
- self.assert_no_uncommitted_changes(build_repository_folder)
1339
-
1340
- repository_folder = GeneralUtilities.resolve_relative_path(f"Submodules{os.path.sep}{createRelease_configuration.repository_folder_name}", build_repository_folder)
1341
- mergeInformation = MergeToStableBranchInformationForProjectInCommonProjectFormat(repository_folder, createRelease_configuration.additional_arguments_file, createRelease_configuration.artifacts_folder)
1342
- createReleaseInformation = CreateReleaseInformationForProjectInCommonProjectFormat(repository_folder, createRelease_configuration.artifacts_folder, createRelease_configuration.projectname, createRelease_configuration.public_repository_url, mergeInformation.targetbranch, mergeInformation.additional_arguments_file, mergeInformation.export_target, createRelease_configuration.push_artifacts_scripts_folder)
1343
- createReleaseInformation.verbosity = createRelease_configuration.verbosity
1344
-
1345
- self.__sc.git_checkout(build_repository_folder, createRelease_configuration.build_repository_branch)
1346
- self.__sc.git_checkout(createReleaseInformation.reference_repository, createRelease_configuration.reference_repository_branch_name)
1347
-
1348
- self.__sc.assert_is_git_repository(repository_folder)
1349
- self.__sc.assert_is_git_repository(createReleaseInformation.reference_repository)
1350
-
1351
- # TODO check if repository_folder-merge-source-branch and repository_folder-merge-target-branch have different commits
1352
- self.assert_no_uncommitted_changes(repository_folder)
1353
- mergeInformation.verbosity = createRelease_configuration.verbosity
1354
- mergeInformation.push_target_branch = createRelease_configuration.remotename is not None
1355
- mergeInformation.push_target_branch_remote_name = createRelease_configuration.remotename
1356
- mergeInformation.push_source_branch = createRelease_configuration.remotename is not None
1357
- mergeInformation.push_source_branch_remote_name = createRelease_configuration.remotename
1358
- new_project_version = self.__standardized_tasks_merge_to_stable_branch(mergeInformation)
1359
-
1360
- self.__standardized_tasks_release_artifact(createReleaseInformation)
1361
-
1362
- GeneralUtilities.assert_condition(createRelease_configuration.reference_repository_remote_name is not None, "Remote for reference-repository not set.")
1363
- self.__sc.git_commit(createReleaseInformation.reference_repository, f"Added reference of {createRelease_configuration.projectname} v{new_project_version}")
1364
- self.__sc.git_push_with_retry(createReleaseInformation.reference_repository, createRelease_configuration.reference_repository_remote_name, createRelease_configuration.reference_repository_branch_name, createRelease_configuration.reference_repository_branch_name, verbosity=createRelease_configuration.verbosity)
1365
- self.__sc.git_commit(build_repository_folder, f"Added {createRelease_configuration.projectname} release v{new_project_version}")
1366
- GeneralUtilities.write_message_to_stdout(f"Finished release for project {createRelease_configuration.projectname} v{new_project_version} successfully.")
1367
- return new_project_version
1368
-
1369
- @GeneralUtilities.check_arguments
1370
- def create_release_starter_for_repository_in_standardized_format(self, create_release_file: str, logfile: str, verbosity: int, addLogOverhead: bool, commandline_arguments: list[str]):
1371
- # hint: arguments can be overwritten by commandline_arguments
1372
- folder_of_this_file = os.path.dirname(create_release_file)
1373
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1374
- result = self.__sc.run_program("python", f"CreateRelease.py --overwrite_verbosity {str(verbosity)}", folder_of_this_file, verbosity=verbosity, log_file=logfile, addLogOverhead=addLogOverhead, print_live_output=True, throw_exception_if_exitcode_is_not_zero=False)
1375
- if result[0] != 0:
1376
- raise ValueError(f"CreateRelease.py resulted in exitcode {result[0]}.")
1377
-
1378
- @GeneralUtilities.check_arguments
1379
- def __standardized_tasks_merge_to_stable_branch(self, information: MergeToStableBranchInformationForProjectInCommonProjectFormat) -> str:
1380
- src_branch_commit_id = self.__sc.git_get_commit_id(information.repository, information.sourcebranch)
1381
- if (src_branch_commit_id == self.__sc.git_get_commit_id(information.repository, information.targetbranch)):
1382
- raise ValueError(f"Can not merge because the source-branch and the target-branch are on the same commit (commit-id: {src_branch_commit_id})")
1383
-
1384
- self.assert_no_uncommitted_changes(information.repository)
1385
- self.__sc.git_checkout(information.repository, information.sourcebranch)
1386
- self.__sc.run_program("git", "clean -dfx", information.repository, verbosity=information.verbosity, throw_exception_if_exitcode_is_not_zero=True)
1387
- project_version = self.__sc.get_semver_version_from_gitversion(information.repository)
1388
-
1389
- self.build_codeunits(information.repository, information.verbosity, information.target_environmenttype_for_qualitycheck, information.additional_arguments_file, False, information.export_target, [], True, "Productive build") # verify hat codeunits are buildable with productive-config before merge
1390
-
1391
- self.assert_no_uncommitted_changes(information.repository)
1392
-
1393
- commit_id = self.__sc.git_merge(information.repository, information.sourcebranch, information.targetbranch, True, True)
1394
- self.__sc.git_create_tag(information.repository, commit_id, f"v{project_version}", information.sign_git_tags)
1395
-
1396
- if information.push_source_branch:
1397
- GeneralUtilities.write_message_to_stdout("Push source-branch...")
1398
- self.__sc.git_push_with_retry(information.repository, information.push_source_branch_remote_name, information.sourcebranch, information.sourcebranch, pushalltags=True, verbosity=information.verbosity)
1399
-
1400
- if information.push_target_branch:
1401
- GeneralUtilities.write_message_to_stdout("Push target-branch...")
1402
- self.__sc.git_push_with_retry(information.repository, information.push_target_branch_remote_name, information.targetbranch, information.targetbranch, pushalltags=True, verbosity=information.verbosity)
1403
-
1404
- return project_version
1405
-
1406
- @GeneralUtilities.check_arguments
1407
- def standardized_tasks_build_for_docker_project(self, build_script_file: str, target_environment_type: str, verbosity: int, commandline_arguments: list[str], custom_arguments: dict[str, str] = None) -> None:
1408
- self.standardized_tasks_build_for_docker_project_with_additional_build_arguments(build_script_file, target_environment_type, verbosity, commandline_arguments, custom_arguments)
1409
- self.generate_sbom_for_docker_image(build_script_file, verbosity, commandline_arguments)
1410
-
1411
- @GeneralUtilities.check_arguments
1412
- def merge_sbom_file_from_dependent_codeunit_into_this(self, build_script_file: str, dependent_codeunit_name: str) -> None:
1413
- codeunitname: str = Path(os.path.dirname(build_script_file)).parent.parent.name
1414
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", str(os.path.dirname(build_script_file)))
1415
- repository_folder = GeneralUtilities.resolve_relative_path("..", codeunit_folder)
1416
- dependent_codeunit_folder = os.path.join(repository_folder, dependent_codeunit_name).replace("\\", "/")
1417
- t = TasksForCommonProjectStructure()
1418
- sbom_file = f"{repository_folder}/{codeunitname}/Other/Artifacts/BOM/{codeunitname}.{t.get_version_of_codeunit_folder(codeunit_folder)}.sbom.xml"
1419
- dependent_sbom_file = f"{repository_folder}/{dependent_codeunit_name}/Other/Artifacts/BOM/{dependent_codeunit_name}.{t.get_version_of_codeunit_folder(dependent_codeunit_folder)}.sbom.xml"
1420
- self.merge_sbom_file(repository_folder, dependent_sbom_file, sbom_file)
1421
-
1422
- @GeneralUtilities.check_arguments
1423
- def merge_sbom_file(self, repository_folder: str, source_sbom_file_relative: str, target_sbom_file_relative: str) -> None:
1424
- GeneralUtilities.assert_file_exists(os.path.join(repository_folder, source_sbom_file_relative))
1425
- GeneralUtilities.assert_file_exists(os.path.join(repository_folder, target_sbom_file_relative))
1426
- target_original_sbom_file_relative = os.path.dirname(target_sbom_file_relative)+"/"+os.path.basename(target_sbom_file_relative)+".original.xml"
1427
- os.rename(os.path.join(repository_folder, target_sbom_file_relative), os.path.join(repository_folder, target_original_sbom_file_relative))
1428
-
1429
- self.ensure_cyclonedxcli_is_available(repository_folder)
1430
- cyclonedx_exe = os.path.join(repository_folder, "Other/Resources/CycloneDXCLI/cyclonedx-cli")
1431
- if GeneralUtilities.current_system_is_windows():
1432
- cyclonedx_exe = cyclonedx_exe+".exe"
1433
- self.__sc.run_program(cyclonedx_exe, f"merge --input-files {source_sbom_file_relative} {target_original_sbom_file_relative} --output-file {target_sbom_file_relative}", repository_folder)
1434
- GeneralUtilities.ensure_file_does_not_exist(os.path.join(repository_folder, target_original_sbom_file_relative))
1435
- self.__sc.format_xml_file(os.path.join(repository_folder, target_sbom_file_relative))
1436
-
1437
- @GeneralUtilities.check_arguments
1438
- def standardized_tasks_build_for_docker_project_with_additional_build_arguments(self, build_script_file: str, target_environment_type: str, verbosity: int, commandline_arguments: list[str], custom_arguments: dict[str, str]) -> None:
1439
- use_cache: bool = False
1440
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1441
- codeunitname: str = Path(os.path.dirname(build_script_file)).parent.parent.name
1442
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", str(os.path.dirname(build_script_file)))
1443
- codeunitname_lower = codeunitname.lower()
1444
- codeunit_file = os.path.join(codeunit_folder, f"{codeunitname}.codeunit.xml")
1445
- codeunitversion = self.get_version_of_codeunit(codeunit_file)
1446
- args = ["image", "build", "--pull", "--force-rm", "--progress=plain", "--build-arg", f"TargetEnvironmentType={target_environment_type}", "--build-arg", f"CodeUnitName={codeunitname}", "--build-arg", f"CodeUnitVersion={codeunitversion}", "--build-arg", f"CodeUnitOwnerName={self.get_codeunit_owner_name(codeunit_file)}", "--build-arg", f"CodeUnitOwnerEMailAddress={self.get_codeunit_owner_emailaddress(codeunit_file)}"]
1447
- if custom_arguments is not None:
1448
- for custom_argument_key, custom_argument_value in custom_arguments.items():
1449
- args.append("--build-arg")
1450
- args.append(f"{custom_argument_key}={custom_argument_value}")
1451
- args = args+["--tag", f"{codeunitname_lower}:latest", "--tag", f"{codeunitname_lower}:{codeunitversion}", "--file", f"{codeunitname}/Dockerfile"]
1452
- if not use_cache:
1453
- args.append("--no-cache")
1454
- args.append(".")
1455
- codeunit_content_folder = os.path.join(codeunit_folder)
1456
- self.__sc.run_program_argsasarray("docker", args, codeunit_content_folder, verbosity=verbosity, print_errors_as_information=True)
1457
- artifacts_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts", codeunit_folder)
1458
- app_artifacts_folder = os.path.join(artifacts_folder, "BuildResult_OCIImage")
1459
- GeneralUtilities.ensure_directory_does_not_exist(app_artifacts_folder)
1460
- GeneralUtilities.ensure_directory_exists(app_artifacts_folder)
1461
- self.__sc.run_program_argsasarray("docker", ["save", "--output", f"{codeunitname}_v{codeunitversion}.tar", f"{codeunitname_lower}:{codeunitversion}"], app_artifacts_folder, verbosity=verbosity, print_errors_as_information=True)
1462
- self.copy_source_files_to_output_directory(build_script_file)
1463
-
1464
- @GeneralUtilities.check_arguments
1465
- def generate_sbom_for_docker_image(self, build_script_file: str, verbosity: int, commandline_arguments: list[str]) -> None:
1466
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1467
- codeunitname: str = Path(os.path.dirname(build_script_file)).parent.parent.name
1468
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", str(os.path.dirname(build_script_file)))
1469
- artifacts_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts", codeunit_folder)
1470
- codeunitname_lower = codeunitname.lower()
1471
- sbom_folder = os.path.join(artifacts_folder, "BOM")
1472
- codeunitversion = self.get_version_of_codeunit(os.path.join(codeunit_folder, f"{codeunitname}.codeunit.xml"))
1473
- GeneralUtilities.ensure_directory_exists(sbom_folder)
1474
- self.__sc.run_program_argsasarray("docker", ["sbom", "--format", "cyclonedx", f"{codeunitname_lower}:{codeunitversion}", "--output", f"{codeunitname}.{codeunitversion}.sbom.xml"], sbom_folder, verbosity=verbosity, print_errors_as_information=True)
1475
- self.__sc.format_xml_file(sbom_folder+f"/{codeunitname}.{codeunitversion}.sbom.xml")
1476
-
1477
- @GeneralUtilities.check_arguments
1478
- def push_docker_build_artifact(self, push_artifacts_file: str, registry: str, verbosity: int, push_readme: bool, commandline_arguments: list[str], repository_folder_name: str, remote_image_name: str = None) -> None:
1479
- folder_of_this_file = os.path.dirname(push_artifacts_file)
1480
- filename = os.path.basename(push_artifacts_file)
1481
- codeunitname_regex: str = "([a-zA-Z0-9]+)"
1482
- filename_regex: str = f"PushArtifacts\\.{codeunitname_regex}\\.py"
1483
- if match := re.search(filename_regex, filename, re.IGNORECASE):
1484
- codeunitname = match.group(1)
1485
- else:
1486
- raise ValueError(f"Expected push-artifacts-file to match the regex \"{filename_regex}\" where \"{codeunitname_regex}\" represents the codeunit-name.")
1487
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1488
- repository_folder = GeneralUtilities.resolve_relative_path(f"..{os.path.sep}..{os.path.sep}Submodules{os.path.sep}{repository_folder_name}", folder_of_this_file)
1489
- codeunit_folder = os.path.join(repository_folder, codeunitname)
1490
- artifacts_folder = self.get_artifacts_folder(repository_folder, codeunitname)
1491
- applicationimage_folder = os.path.join(artifacts_folder, "BuildResult_OCIImage")
1492
- image_file = self.__sc.find_file_by_extension(applicationimage_folder, "tar")
1493
- image_filename = os.path.basename(image_file)
1494
- codeunit_version = self.get_version_of_codeunit(os.path.join(codeunit_folder, f"{codeunitname}.codeunit.xml"))
1495
- if remote_image_name is None:
1496
- remote_image_name = codeunitname
1497
- remote_image_name = remote_image_name.lower()
1498
- local_image_name = codeunitname.lower()
1499
- remote_repo = f"{registry}/{remote_image_name}"
1500
- remote_image_latest = f"{remote_repo}:latest"
1501
- remote_image_version = f"{remote_repo}:{codeunit_version}"
1502
- GeneralUtilities.write_message_to_stdout("Load image...")
1503
- self.__sc.run_program("docker", f"load --input {image_filename}", applicationimage_folder, verbosity=verbosity)
1504
- GeneralUtilities.write_message_to_stdout("Tag image...")
1505
- self.__sc.run_program_with_retry("docker", f"tag {local_image_name}:{codeunit_version} {remote_image_latest}", verbosity=verbosity)
1506
- self.__sc.run_program_with_retry("docker", f"tag {local_image_name}:{codeunit_version} {remote_image_version}", verbosity=verbosity)
1507
- GeneralUtilities.write_message_to_stdout("Push image...")
1508
- self.__sc.run_program_with_retry("docker", f"push {remote_image_latest}", verbosity=verbosity)
1509
- self.__sc.run_program_with_retry("docker", f"push {remote_image_version}", verbosity=verbosity)
1510
- if push_readme:
1511
- self.__sc.run_program_with_retry("docker-pushrm", f"{remote_repo}", codeunit_folder, verbosity=verbosity)
1512
-
1513
- @GeneralUtilities.check_arguments
1514
- def get_dependent_code_units(self, codeunit_file: str) -> list[str]:
1515
- root: etree._ElementTree = etree.parse(codeunit_file)
1516
- result = set(root.xpath('//cps:dependentcodeunit/text()', namespaces={'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'}))
1517
- result = sorted(result)
1518
- return result
1519
-
1520
- @GeneralUtilities.check_arguments
1521
- def dependent_codeunit_exists(self, repository: str, codeunit: str) -> None:
1522
- codeunit_file = f"{repository}/{codeunit}/{codeunit}.codeunit.xml"
1523
- return os.path.isfile(codeunit_file)
1524
-
1525
- @GeneralUtilities.check_arguments
1526
- def standardized_tasks_linting_for_docker_project(self, linting_script_file: str, verbosity: int, targetenvironmenttype: str, commandline_arguments: list[str]) -> None:
1527
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1528
- # TODO check if there are errors in sarif-file
1529
-
1530
- @GeneralUtilities.check_arguments
1531
- def copy_licence_file(self, common_tasks_scripts_file: str) -> None:
1532
- folder_of_current_file = os.path.dirname(common_tasks_scripts_file)
1533
- license_file = GeneralUtilities.resolve_relative_path("../../License.txt", folder_of_current_file)
1534
- target_folder = GeneralUtilities.resolve_relative_path("Artifacts/License", folder_of_current_file)
1535
- GeneralUtilities.ensure_directory_exists(target_folder)
1536
- shutil.copy(license_file, target_folder)
1537
-
1538
- @GeneralUtilities.check_arguments
1539
- def take_readmefile_from_main_readmefile_of_repository(self, common_tasks_scripts_file: str) -> None:
1540
- folder_of_current_file = os.path.dirname(common_tasks_scripts_file)
1541
- source_file = GeneralUtilities.resolve_relative_path("../../ReadMe.md", folder_of_current_file)
1542
- target_file = GeneralUtilities.resolve_relative_path("../ReadMe.md", folder_of_current_file)
1543
- GeneralUtilities.ensure_file_does_not_exist(target_file)
1544
- shutil.copyfile(source_file, target_file)
1545
-
1546
- @GeneralUtilities.check_arguments
1547
- def standardized_tasks_do_common_tasks(self, common_tasks_scripts_file: str, codeunit_version: str, verbosity: int, targetenvironmenttype: str, clear_artifacts_folder: bool, additional_arguments_file: str, assume_dependent_codeunits_are_already_built: bool, commandline_arguments: list[str]) -> None:
1548
- additional_arguments_file = self.get_additionalargumentsfile_from_commandline_arguments(commandline_arguments, additional_arguments_file)
1549
- target_environmenttype = self.get_targetenvironmenttype_from_commandline_arguments(commandline_arguments, targetenvironmenttype) # pylint: disable=unused-variable
1550
- # assume_dependent_codeunits_are_already_built = self.get_assume_dependent_codeunits_are_already_built_from_commandline_arguments(commandline_arguments, assume_dependent_codeunits_are_already_built)
1551
- if commandline_arguments is None:
1552
- raise ValueError('The "commandline_arguments"-parameter is not defined.')
1553
- if len(commandline_arguments) == 0:
1554
- raise ValueError('An empty array as argument for the "commandline_arguments"-parameter is not valid.')
1555
- commandline_arguments = commandline_arguments[1:]
1556
- repository_folder: str = str(Path(os.path.dirname(common_tasks_scripts_file)).parent.parent.absolute())
1557
- self.__sc.assert_is_git_repository(repository_folder)
1558
- codeunit_name: str = str(os.path.basename(Path(os.path.dirname(common_tasks_scripts_file)).parent.absolute()))
1559
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1560
- project_version = self.get_version_of_project(repository_folder)
1561
- codeunit_folder = os.path.join(repository_folder, codeunit_name)
1562
-
1563
- # check codeunit-conformity
1564
- # TODO check if foldername=="<codeunitname>[.codeunit.xml]" == <codeunitname> in file
1565
- supported_codeunitspecificationversion = "2.9.4" # should always be the latest version of the ProjectTemplates-repository
1566
- codeunit_file = os.path.join(codeunit_folder, f"{codeunit_name}.codeunit.xml")
1567
- if not os.path.isfile(codeunit_file):
1568
- raise ValueError(f'Codeunitfile "{codeunit_file}" does not exist.')
1569
- # TODO implement usage of self.reference_latest_version_of_xsd_when_generating_xml
1570
- namespaces = {'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
1571
- root: etree._ElementTree = etree.parse(codeunit_file)
1572
-
1573
- # check codeunit-spcecification-version
1574
- try:
1575
- codeunit_file_version = root.xpath('//cps:codeunit/@codeunitspecificationversion', namespaces=namespaces)[0]
1576
- if codeunit_file_version != supported_codeunitspecificationversion:
1577
- raise ValueError(f"ScriptCollection only supports processing codeunits with codeunit-specification-version={supported_codeunitspecificationversion}.")
1578
- schemaLocation = root.xpath('//cps:codeunit/@xsi:schemaLocation', namespaces=namespaces)[0]
1579
- xmlschema.validate(codeunit_file, schemaLocation)
1580
- # TODO check if the properties codeunithastestablesourcecode, codeunithasupdatabledependencies, throwexceptionifcodeunitfilecannotbevalidated, developmentState and description exist and the values are valid
1581
- except Exception as exception:
1582
- if self.codeunit_throws_exception_if_codeunitfile_is_not_validatable(codeunit_file):
1583
- raise exception
1584
- else:
1585
- GeneralUtilities.write_message_to_stderr(f'Warning: Codeunitfile "{codeunit_file}" can not be validated due to the following exception:')
1586
- GeneralUtilities.write_exception_to_stderr(exception)
1587
-
1588
- # check codeunit-name
1589
- codeunit_name_in_codeunit_file = root.xpath('//cps:codeunit/cps:name/text()', namespaces=namespaces)[0]
1590
- if codeunit_name != codeunit_name_in_codeunit_file:
1591
- raise ValueError(f"The folder-name ('{codeunit_name}') is not equal to the codeunit-name ('{codeunit_name_in_codeunit_file}').")
1592
-
1593
- # check owner-name
1594
- codeunit_ownername_in_codeunit_file = self. get_codeunit_owner_name(codeunit_file)
1595
- GeneralUtilities.assert_condition(GeneralUtilities.string_has_content(codeunit_ownername_in_codeunit_file), "No valid name for codeunitowner given.")
1596
-
1597
- # check owner-emailaddress
1598
- codeunit_owneremailaddress_in_codeunit_file = self.get_codeunit_owner_emailaddress(codeunit_file)
1599
- GeneralUtilities.assert_condition(GeneralUtilities.string_has_content(codeunit_owneremailaddress_in_codeunit_file), "No valid email-address for codeunitowner given.")
1600
-
1601
- # check development-state
1602
- developmentstate = root.xpath('//cps:properties/@developmentstate', namespaces=namespaces)[0]
1603
- developmentstate_active = "Active development"
1604
- developmentstate_maintenance = "Maintenance-updates only"
1605
- developmentstate_inactive = "Inactive"
1606
- GeneralUtilities.assert_condition(developmentstate in (developmentstate_active, developmentstate_maintenance, developmentstate_inactive), f"Invalid development-state. Must be '{developmentstate_active}' or '{developmentstate_maintenance}' or '{developmentstate_inactive}' but was '{developmentstate}'.")
1607
-
1608
- # check for mandatory files
1609
- files = ["Other/Build/Build.py", "Other/QualityCheck/Linting.py", "Other/Reference/GenerateReference.py"]
1610
- if self.codeunit_has_testable_sourcecode(codeunit_file):
1611
- # TODO check if the testsettings-section appears in the codeunit-file
1612
- files.append("Other/QualityCheck/RunTestcases.py")
1613
- if self.codeunit_has_updatable_dependencies(codeunit_file):
1614
- # TODO check if the updatesettings-section appears in the codeunit-file
1615
- files.append("Other/UpdateDependencies.py")
1616
- for file in files:
1617
- combined_file = os.path.join(codeunit_folder, file)
1618
- if not os.path.isfile(combined_file):
1619
- raise ValueError(f'The mandatory file "{file}" does not exist in the codeunit-folder.')
1620
-
1621
- if os.path.isfile(os.path.join(codeunit_folder, "Other", "requirements.txt")):
1622
- self.install_requirementstxt_for_codeunit(codeunit_folder, verbosity)
1623
-
1624
- # check developer
1625
- if self.validate_developers_of_repository:
1626
- expected_authors: list[tuple[str, str]] = []
1627
- expected_authors_in_xml = root.xpath('//cps:codeunit/cps:developerteam/cps:developer', namespaces=namespaces)
1628
- for expected_author in expected_authors_in_xml:
1629
- author_name = expected_author.xpath('./cps:developername/text()', namespaces=namespaces)[0]
1630
- author_emailaddress = expected_author.xpath('./cps:developeremailaddress/text()', namespaces=namespaces)[0]
1631
- expected_authors.append((author_name, author_emailaddress))
1632
- actual_authors: list[tuple[str, str]] = self.__sc.get_all_authors_and_committers_of_repository(repository_folder, codeunit_name, verbosity)
1633
- # TODO refactor this check to only check commits which are behind this but which are not already on main
1634
- # TODO verify also if the commit is signed by a valid key of the author
1635
- for actual_author in actual_authors:
1636
- if not (actual_author) in expected_authors:
1637
- actual_author_formatted = f"{actual_author[0]} <{actual_author[1]}>"
1638
- raise ValueError(f'Author/Comitter "{actual_author_formatted}" is not in the codeunit-developer-team. If {actual_author} is a authorized developer for this codeunit you should consider defining this in the codeunit-file or adapting the name using a .mailmap-file (see https://git-scm.com/docs/gitmailmap). The developer-team-check can also be disabled using the property validate_developers_of_repository.')
1639
-
1640
- dependent_codeunits = self.get_dependent_code_units(codeunit_file)
1641
- for dependent_codeunit in dependent_codeunits:
1642
- if not self.dependent_codeunit_exists(repository_folder, dependent_codeunit):
1643
- raise ValueError(f"Codeunit {codeunit_name} does have dependent codeunit {dependent_codeunit} which does not exist.")
1644
-
1645
- # TODO implement cycle-check for dependent codeunits
1646
-
1647
- # clear previously builded artifacts if desired:
1648
- if clear_artifacts_folder:
1649
- artifacts_folder = os.path.join(codeunit_folder, "Other", "Artifacts")
1650
- GeneralUtilities.ensure_directory_does_not_exist(artifacts_folder)
1651
-
1652
- # get artifacts from dependent codeunits
1653
- # if assume_dependent_codeunits_are_already_built:
1654
- # self.build_dependent_code_units(repository_folder, codeunit_name, verbosity, target_environmenttype, additional_arguments_file, commandline_arguments)
1655
- self.copy_artifacts_from_dependent_code_units(repository_folder, codeunit_name)
1656
-
1657
- # update codeunit-version
1658
- self.update_version_of_codeunit(common_tasks_scripts_file, codeunit_version)
1659
-
1660
- # set project version
1661
- package_json_file = os.path.join(repository_folder, "package.json") # TDOO move this to a general project-specific (and codeunit-independent-script)
1662
- if os.path.isfile(package_json_file):
1663
- package_json_data: str = None
1664
- with open(package_json_file, "r", encoding="utf-8") as f1:
1665
- package_json_data = json.load(f1)
1666
- package_json_data["version"] = project_version
1667
- with open(package_json_file, "w", encoding="utf-8") as f2:
1668
- json.dump(package_json_data, f2, indent=2)
1669
- GeneralUtilities.write_text_to_file(package_json_file, GeneralUtilities.read_text_from_file(package_json_file).replace("\r", ""))
1670
-
1671
- # set default constants
1672
- self.set_default_constants(os.path.join(codeunit_folder))
1673
-
1674
- # Copy changelog-file
1675
- changelog_folder = os.path.join(repository_folder, "Other", "Resources", "Changelog")
1676
- changelog_file = os.path.join(changelog_folder, f"v{project_version}.md")
1677
- target_folder = os.path.join(codeunit_folder, "Other", "Artifacts", "Changelog")
1678
- GeneralUtilities.ensure_directory_exists(target_folder)
1679
- shutil.copy(changelog_file, target_folder)
1680
-
1681
- # Hints-file
1682
- hints_file = os.path.join(codeunit_folder, "Other", "Reference", "ReferenceContent", "Hints.md")
1683
- if not os.path.isfile(hints_file):
1684
- raise ValueError(f"Hints-file '{hints_file}' does not exist.")
1685
-
1686
- # Copy license-file
1687
- self.copy_licence_file(common_tasks_scripts_file)
1688
-
1689
- # Generate diff-report
1690
- self.generate_diff_report(repository_folder, codeunit_name, codeunit_version)
1691
-
1692
- @GeneralUtilities.check_arguments
1693
- def __suport_information_exists(self, repository_folder: str, version_of_product: str) -> bool:
1694
- self.__sc.assert_is_git_repository(repository_folder)
1695
- folder = os.path.join(repository_folder, "Other", "Resources", "Support")
1696
- file = os.path.join(folder, "InformationAboutSupportedVersions.csv")
1697
- if not os.path.isfile(file):
1698
- return False
1699
- entries = GeneralUtilities.read_csv_file(file, True)
1700
- for entry in entries:
1701
- if entry[0] == version_of_product:
1702
- return True
1703
- return False
1704
-
1705
- @GeneralUtilities.check_arguments
1706
- def get_versions(self, repository_folder: str) -> list[tuple[str, datetime, datetime]]:
1707
- self.__sc.assert_is_git_repository(repository_folder)
1708
- folder = os.path.join(repository_folder, "Other", "Resources", "Support")
1709
- file = os.path.join(folder, "InformationAboutSupportedVersions.csv")
1710
- result: list[(str, datetime, datetime)] = list[(str, datetime, datetime)]()
1711
- if not os.path.isfile(file):
1712
- return result
1713
- entries = GeneralUtilities.read_csv_file(file, True)
1714
- for entry in entries:
1715
- d1 = GeneralUtilities.string_to_datetime(entry[1])
1716
- if d1.tzinfo is None:
1717
- d1 = d1.replace(tzinfo=timezone.utc)
1718
- d2 = GeneralUtilities.string_to_datetime(entry[2])
1719
- if d2.tzinfo is None:
1720
- d2 = d2.replace(tzinfo=timezone.utc)
1721
- result.append((entry[0], d1, d2))
1722
- return result
1723
-
1724
- @GeneralUtilities.check_arguments
1725
- def get_supported_versions(self, repository_folder: str, moment: datetime) -> list[tuple[str, datetime, datetime]]:
1726
- self.__sc.assert_is_git_repository(repository_folder)
1727
- result: list[tuple[str, datetime, datetime]] = list[tuple[str, datetime, datetime]]()
1728
- for entry in self.get_versions(repository_folder):
1729
- if entry[1] <= moment and moment <= entry[2]:
1730
- result.append(entry)
1731
- return result
1732
-
1733
- @GeneralUtilities.check_arguments
1734
- def get_unsupported_versions(self, repository_folder: str, moment: datetime) -> list[tuple[str, datetime, datetime]]:
1735
- self.__sc.assert_is_git_repository(repository_folder)
1736
- result: list[tuple[str, datetime, datetime]] = list[tuple[str, datetime, datetime]]()
1737
- for entry in self.get_versions(repository_folder):
1738
- if not (entry[1] <= moment and moment <= entry[2]):
1739
- result.append(entry)
1740
- return result
1741
-
1742
- @GeneralUtilities.check_arguments
1743
- def mark_current_version_as_supported(self, repository_folder: str, version_of_product: str, supported_from: datetime, supported_until: datetime):
1744
- self.__sc.assert_is_git_repository(repository_folder)
1745
- if self.__suport_information_exists(repository_folder, version_of_product):
1746
- raise ValueError(f"Version-support for v{version_of_product} already defined.")
1747
- folder = os.path.join(repository_folder, "Other", "Resources", "Support")
1748
- GeneralUtilities.ensure_directory_exists(folder)
1749
- file = os.path.join(folder, "InformationAboutSupportedVersions.csv")
1750
- if not os.path.isfile(file):
1751
- GeneralUtilities.ensure_file_exists(file)
1752
- GeneralUtilities.append_line_to_file(file, "Version;SupportBegin;SupportEnd")
1753
- GeneralUtilities.append_line_to_file(file, f"{version_of_product};{GeneralUtilities.datetime_to_string(supported_from)};{GeneralUtilities.datetime_to_string(supported_until)}")
1754
-
1755
- @GeneralUtilities.check_arguments
1756
- def get_codeunit_owner_name(self, codeunit_file: str) -> None:
1757
- namespaces = {'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
1758
- root: etree._ElementTree = etree.parse(codeunit_file)
1759
- result = root.xpath('//cps:codeunit/cps:codeunitownername/text()', namespaces=namespaces)[0]
1760
- return result
1761
-
1762
- @GeneralUtilities.check_arguments
1763
- def get_codeunit_owner_emailaddress(self, codeunit_file: str) -> None:
1764
- namespaces = {'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
1765
- root: etree._ElementTree = etree.parse(codeunit_file)
1766
- result = root.xpath('//cps:codeunit/cps:codeunitowneremailaddress/text()', namespaces=namespaces)[0]
1767
- return result
1768
-
1769
- @GeneralUtilities.check_arguments
1770
- def generate_diff_report(self, repository_folder: str, codeunit_name: str, current_version: str) -> None:
1771
- self.__sc.assert_is_git_repository(repository_folder)
1772
- codeunit_folder = os.path.join(repository_folder, codeunit_name)
1773
- target_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts/DiffReport", codeunit_folder)
1774
- GeneralUtilities.ensure_directory_does_not_exist(target_folder)
1775
- GeneralUtilities.ensure_directory_exists(target_folder)
1776
- target_file_light = os.path.join(target_folder, "DiffReport.html").replace("\\", "/")
1777
- target_file_dark = os.path.join(target_folder, "DiffReportDark.html").replace("\\", "/")
1778
- src = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" # hash/id of empty git-tree
1779
- src_prefix = "Begin"
1780
- if self.__sc.get_current_git_branch_has_tag(repository_folder):
1781
- latest_tag = self.__sc.get_latest_git_tag(repository_folder)
1782
- src = self.__sc.git_get_commitid_of_tag(repository_folder, latest_tag)
1783
- src_prefix = latest_tag
1784
- dst = "HEAD"
1785
- dst_prefix = f"v{current_version}"
1786
-
1787
- temp_file = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
1788
- try:
1789
- GeneralUtilities.ensure_file_does_not_exist(temp_file)
1790
- GeneralUtilities.write_text_to_file(temp_file, self.__sc.run_program("git", f'--no-pager diff --src-prefix={src_prefix}/ --dst-prefix={dst_prefix}/ {src} {dst} -- {codeunit_name}', repository_folder)[1])
1791
- self.__sc.run_program_argsasarray("pygmentize", ['-l', 'diff', '-f', 'html', '-O', 'full', '-o', target_file_light, '-P', 'style=default', temp_file], repository_folder)
1792
- self.__sc.run_program_argsasarray("pygmentize", ['-l', 'diff', '-f', 'html', '-O', 'full', '-o', target_file_dark, '-P', 'style=github-dark', temp_file], repository_folder)
1793
- finally:
1794
- GeneralUtilities.ensure_file_does_not_exist(temp_file)
1795
-
1796
- @GeneralUtilities.check_arguments
1797
- def get_version_of_project(self, repository_folder: str) -> str:
1798
- self.__sc.assert_is_git_repository(repository_folder)
1799
- return self.__sc.get_semver_version_from_gitversion(repository_folder)
1800
-
1801
- @GeneralUtilities.check_arguments
1802
- def replace_common_variables_in_nuspec_file(self, codeunit_folder: str) -> None:
1803
- self.assert_is_codeunit_folder(codeunit_folder)
1804
- codeunit_name = os.path.basename(codeunit_folder)
1805
- codeunit_version = self.get_version_of_codeunit_folder(codeunit_folder)
1806
- nuspec_file = os.path.join(codeunit_folder, "Other", "Build", f"{codeunit_name}.nuspec")
1807
- self.__sc.replace_version_in_nuspec_file(nuspec_file, codeunit_version)
1808
-
1809
- @GeneralUtilities.check_arguments
1810
- def standardized_tasks_build_for_angular_codeunit(self, build_script_file: str, build_environment_target_type: str, verbosity: int, commandline_arguments: list[str]) -> None:
1811
- build_script_folder = os.path.dirname(build_script_file)
1812
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", build_script_folder)
1813
- GeneralUtilities.ensure_directory_does_not_exist(f"{codeunit_folder}/.angular")
1814
- self.standardized_tasks_build_for_node_codeunit(build_script_file, build_environment_target_type, verbosity, commandline_arguments)
1815
-
1816
- @GeneralUtilities.check_arguments
1817
- def standardized_tasks_build_for_node_codeunit(self, build_script_file: str, build_environment_target_type: str, verbosity: int, commandline_arguments: list[str]) -> None:
1818
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1819
- build_script_folder = os.path.dirname(build_script_file)
1820
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", build_script_folder)
1821
- self.run_with_epew("npm", f"run build-{build_environment_target_type}", codeunit_folder, verbosity=verbosity)
1822
- self.standardized_tasks_build_bom_for_node_project(codeunit_folder, verbosity, commandline_arguments)
1823
- self.copy_source_files_to_output_directory(build_script_file)
1824
-
1825
- @GeneralUtilities.check_arguments
1826
- def standardized_tasks_build_bom_for_node_project(self, codeunit_folder: str, verbosity: int, commandline_arguments: list[str]) -> None:
1827
- self.assert_is_codeunit_folder(codeunit_folder)
1828
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1829
- relative_path_to_bom_file = f"Other/Artifacts/BOM/{os.path.basename(codeunit_folder)}.{self.get_version_of_codeunit_folder(codeunit_folder)}.sbom.xml"
1830
- self.run_with_epew("cyclonedx-npm", f"--output-format xml --output-file {relative_path_to_bom_file}", codeunit_folder, verbosity=verbosity)
1831
- self.__sc.format_xml_file(codeunit_folder+"/"+relative_path_to_bom_file)
1832
-
1833
- @GeneralUtilities.check_arguments
1834
- def standardized_tasks_linting_for_angular_codeunit(self, linting_script_file: str, verbosity: int, build_environment_target_type: str, commandline_arguments: list[str]) -> None:
1835
- self.standardized_tasks_linting_for_node_codeunit(linting_script_file, verbosity, build_environment_target_type, commandline_arguments)
1836
-
1837
- @GeneralUtilities.check_arguments
1838
- def standardized_tasks_linting_for_node_codeunit(self, linting_script_file: str, verbosity: int, build_environment_target_type: str, commandline_arguments: list[str]) -> None:
1839
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1840
- build_script_folder = os.path.dirname(linting_script_file)
1841
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", build_script_folder)
1842
- self.run_with_epew("ng", "lint", codeunit_folder, verbosity=verbosity)
1843
-
1844
- @GeneralUtilities.check_arguments
1845
- def standardized_tasks_run_testcases_for_flutter_project_in_common_project_structure(self, script_file: str, verbosity: int, args: list[str], package_name: str, build_environment_target_type: str, generate_badges: bool):
1846
- codeunit_folder = GeneralUtilities.resolve_relative_path("../../..", script_file)
1847
- repository_folder = GeneralUtilities.resolve_relative_path("..", codeunit_folder)
1848
- codeunit_name = os.path.basename(codeunit_folder)
1849
- src_folder = GeneralUtilities.resolve_relative_path(package_name, codeunit_folder)
1850
- verbosity = self.get_verbosity_from_commandline_arguments(args, verbosity)
1851
- self.run_with_epew("flutter", "test --coverage", src_folder, verbosity)
1852
- test_coverage_folder_relative = "Other/Artifacts/TestCoverage"
1853
- test_coverage_folder = GeneralUtilities.resolve_relative_path(test_coverage_folder_relative, codeunit_folder)
1854
- GeneralUtilities.ensure_directory_exists(test_coverage_folder)
1855
- coverage_file_relative = f"{test_coverage_folder_relative}/TestCoverage.xml"
1856
- coverage_file = GeneralUtilities.resolve_relative_path(coverage_file_relative, codeunit_folder)
1857
- self.run_with_epew("lcov_cobertura", f"coverage/lcov.info --base-dir . --excludes test --output ../{coverage_file_relative} --demangle", src_folder, verbosity)
1858
-
1859
- # format correctly
1860
- content = GeneralUtilities.read_text_from_file(coverage_file)
1861
- content = re.sub('<![^<]+>', '', content)
1862
- content = re.sub('\\\\', '/', content)
1863
- content = re.sub('\\ name=\\"lib\\"', '', content)
1864
- content = re.sub('\\ filename=\\"lib/', f' filename="{package_name}/lib/', content)
1865
- GeneralUtilities.write_text_to_file(coverage_file, content)
1866
- self.__testcoverage_for_flutter_project_merge_packages(coverage_file)
1867
- self.__testcoverage_for_flutter_project_calculate_line_rate(coverage_file)
1868
-
1869
- self.run_testcases_common_post_task(repository_folder, codeunit_name, verbosity, generate_badges, build_environment_target_type, args)
1870
-
1871
- def __testcoverage_for_flutter_project_merge_packages(self, coverage_file: str):
1872
- tree = etree.parse(coverage_file)
1873
- root = tree.getroot()
1874
-
1875
- packages = root.findall("./packages/package")
1876
-
1877
- all_classes = []
1878
- for pkg in packages:
1879
- classes = pkg.find("classes")
1880
- if classes is not None:
1881
- all_classes.extend(classes.findall("class"))
1882
- new_package = etree.Element("package", name="Malno")
1883
- new_classes = etree.SubElement(new_package, "classes")
1884
- for cls in all_classes:
1885
- new_classes.append(cls)
1886
- packages_node = root.find("./packages")
1887
- packages_node.clear()
1888
- packages_node.append(new_package)
1889
- tree.write(coverage_file, pretty_print=True, xml_declaration=True, encoding="UTF-8")
1890
-
1891
- def __testcoverage_for_flutter_project_calculate_line_rate(self, coverage_file: str):
1892
- tree = etree.parse(coverage_file)
1893
- root = tree.getroot()
1894
- package = root.find("./packages/package")
1895
- if package is None:
1896
- raise RuntimeError("No <package>-Element found")
1897
-
1898
- line_elements = package.findall(".//line")
1899
-
1900
- amount_of_lines = 0
1901
- amount_of_hited_lines = 0
1902
-
1903
- for line in line_elements:
1904
- amount_of_lines += 1
1905
- hits = int(line.get("hits", "0"))
1906
- if hits > 0:
1907
- amount_of_hited_lines += 1
1908
- line_rate = amount_of_hited_lines / amount_of_lines if amount_of_lines > 0 else 0.0
1909
- package.set("line-rate", str(line_rate))
1910
- tree.write(coverage_file, pretty_print=True, xml_declaration=True, encoding="UTF-8")
1911
-
1912
- @GeneralUtilities.check_arguments
1913
- def standardized_tasks_run_testcases_for_angular_codeunit(self, runtestcases_script_file: str, build_environment_target_type: str, generate_badges: bool, verbosity: int, commandline_arguments: list[str]) -> None:
1914
- # prepare
1915
- codeunit_name: str = os.path.basename(str(Path(os.path.dirname(runtestcases_script_file)).parent.parent.absolute()))
1916
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1917
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", os.path.dirname(runtestcases_script_file))
1918
- repository_folder = os.path.dirname(codeunit_folder)
1919
-
1920
- # run testcases
1921
- self.standardized_tasks_run_testcases_for_node_codeunit(runtestcases_script_file, build_environment_target_type, generate_badges, verbosity, commandline_arguments)
1922
-
1923
- # rename file
1924
- coverage_folder = os.path.join(codeunit_folder, "Other", "Artifacts", "TestCoverage")
1925
- target_file = os.path.join(coverage_folder, "TestCoverage.xml")
1926
- GeneralUtilities.ensure_file_does_not_exist(target_file)
1927
- os.rename(os.path.join(coverage_folder, "cobertura-coverage.xml"), target_file)
1928
- self.__rename_packagename_in_coverage_file(target_file, codeunit_name)
1929
-
1930
- # adapt backslashs to slashs
1931
- content = GeneralUtilities.read_text_from_file(target_file)
1932
- content = re.sub('\\\\', '/', content)
1933
- GeneralUtilities.write_text_to_file(target_file, content)
1934
-
1935
- # aggregate packages in testcoverage-file
1936
- roottree: etree._ElementTree = etree.parse(target_file)
1937
- existing_classes = list(roottree.xpath('//coverage/packages/package/classes/class'))
1938
-
1939
- old_packages_list = roottree.xpath('//coverage/packages/package')
1940
- for package in old_packages_list:
1941
- package.getparent().remove(package)
1942
-
1943
- root = roottree.getroot()
1944
- packages_element = root.find("packages")
1945
- package_element = etree.SubElement(packages_element, "package")
1946
- package_element.attrib['name'] = codeunit_name
1947
- package_element.attrib['lines-valid'] = root.attrib["lines-valid"]
1948
- package_element.attrib['lines-covered'] = root.attrib["lines-covered"]
1949
- package_element.attrib['line-rate'] = root.attrib["line-rate"]
1950
- package_element.attrib['branches-valid'] = root.attrib["branches-valid"]
1951
- package_element.attrib['branches-covered'] = root.attrib["branches-covered"]
1952
- package_element.attrib['branch-rate'] = root.attrib["branch-rate"]
1953
- package_element.attrib['timestamp'] = root.attrib["timestamp"]
1954
- package_element.attrib['complexity'] = root.attrib["complexity"]
1955
-
1956
- classes_element = etree.SubElement(package_element, "classes")
1957
-
1958
- for existing_class in existing_classes:
1959
- classes_element.append(existing_class)
1960
-
1961
- result = etree.tostring(roottree, pretty_print=True).decode("utf-8")
1962
- GeneralUtilities.write_text_to_file(target_file, result)
1963
-
1964
- # post tasks
1965
- self.run_testcases_common_post_task(repository_folder, codeunit_name, verbosity, generate_badges, build_environment_target_type, commandline_arguments)
1966
-
1967
- @GeneralUtilities.check_arguments
1968
- def standardized_tasks_run_testcases_for_node_codeunit(self, runtestcases_script_file: str, build_environment_target_type: str, generate_badges: bool, verbosity: int, commandline_arguments: list[str]) -> None:
1969
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
1970
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", os.path.dirname(runtestcases_script_file))
1971
- self.run_with_epew("npm", f"run test-{build_environment_target_type}", codeunit_folder, verbosity=verbosity)
1972
-
1973
- @GeneralUtilities.check_arguments
1974
- def __rename_packagename_in_coverage_file(self, file: str, codeunit_name: str) -> None:
1975
- root: etree._ElementTree = etree.parse(file)
1976
- packages = root.xpath('//coverage/packages/package')
1977
- for package in packages:
1978
- package.attrib['name'] = codeunit_name
1979
- result = etree.tostring(root).decode("utf-8")
1980
- GeneralUtilities.write_text_to_file(file, result)
1981
-
1982
- @GeneralUtilities.check_arguments
1983
- def do_npm_install(self, package_json_folder: str, force: bool, verbosity: int = 1) -> None:
1984
- argument1 = "install"
1985
- if force:
1986
- argument1 = f"{argument1} --force"
1987
- self.run_with_epew("npm", argument1, package_json_folder, verbosity=verbosity)
1988
-
1989
- argument2 = "install --package-lock-only"
1990
- if force:
1991
- argument2 = f"{argument2} --force"
1992
- self.run_with_epew("npm", argument2, package_json_folder, verbosity=verbosity)
1993
-
1994
- argument3 = "clean-install"
1995
- if force:
1996
- argument3 = f"{argument3} --force"
1997
- self.run_with_epew("npm", argument3, package_json_folder, verbosity=verbosity)
1998
-
1999
- @GeneralUtilities.check_arguments
2000
- def run_with_epew(self, program: str, argument: str = "", working_directory: str = None, verbosity: int = 1, print_errors_as_information: bool = False, log_file: str = None, timeoutInSeconds: int = 600, addLogOverhead: bool = False, title: str = None, log_namespace: str = "", arguments_for_log: list[str] = None, throw_exception_if_exitcode_is_not_zero: bool = True, custom_argument: object = None, interactive: bool = False) -> tuple[int, str, str, int]:
2001
- sc: ScriptCollectionCore = ScriptCollectionCore()
2002
- sc.program_runner = ProgramRunnerEpew()
2003
- return sc.run_program(program, argument, working_directory, verbosity, print_errors_as_information, log_file, timeoutInSeconds, addLogOverhead, title, log_namespace, arguments_for_log, throw_exception_if_exitcode_is_not_zero, custom_argument, interactive)
2004
-
2005
- @GeneralUtilities.check_arguments
2006
- def set_default_constants(self, codeunit_folder: str) -> None:
2007
- self.assert_is_codeunit_folder(codeunit_folder)
2008
- self.set_constant_for_commitid(codeunit_folder)
2009
- self.set_constant_for_commitdate(codeunit_folder)
2010
- self.set_constant_for_codeunitname(codeunit_folder)
2011
- self.set_constant_for_codeunitversion(codeunit_folder)
2012
- self.set_constant_for_codeunitmajorversion(codeunit_folder)
2013
- self.set_constant_for_description(codeunit_folder)
2014
-
2015
- @GeneralUtilities.check_arguments
2016
- def set_constant_for_commitid(self, codeunit_folder: str) -> None:
2017
- self.assert_is_codeunit_folder(codeunit_folder)
2018
- repository = GeneralUtilities.resolve_relative_path("..", codeunit_folder)
2019
- commit_id = self.__sc.git_get_commit_id(repository)
2020
- self.set_constant(codeunit_folder, "CommitId", commit_id)
2021
-
2022
- @GeneralUtilities.check_arguments
2023
- def set_constant_for_commitdate(self, codeunit_folder: str) -> None:
2024
- self.assert_is_codeunit_folder(codeunit_folder)
2025
- repository = GeneralUtilities.resolve_relative_path("..", codeunit_folder)
2026
- commit_date: datetime = self.__sc.git_get_commit_date(repository)
2027
- self.set_constant(codeunit_folder, "CommitDate", GeneralUtilities.datetime_to_string(commit_date))
2028
-
2029
- @GeneralUtilities.check_arguments
2030
- def set_constant_for_codeunitname(self, codeunit_folder: str) -> None:
2031
- self.assert_is_codeunit_folder(codeunit_folder)
2032
- codeunit_name: str = os.path.basename(codeunit_folder)
2033
- self.set_constant(codeunit_folder, "CodeUnitName", codeunit_name)
2034
-
2035
- @GeneralUtilities.check_arguments
2036
- def set_constant_for_codeunitversion(self, codeunit_folder: str) -> None:
2037
- self.assert_is_codeunit_folder(codeunit_folder)
2038
- codeunit_version: str = self.get_version_of_codeunit_folder(codeunit_folder)
2039
- self.set_constant(codeunit_folder, "CodeUnitVersion", codeunit_version)
2040
-
2041
- @GeneralUtilities.check_arguments
2042
- def set_constant_for_codeunitmajorversion(self, codeunit_folder: str) -> None:
2043
- self.assert_is_codeunit_folder(codeunit_folder)
2044
- codeunit_version: str = self.get_version_of_codeunit_folder(codeunit_folder)
2045
- major_version = int(codeunit_version.split(".")[0])
2046
- self.set_constant(codeunit_folder, "CodeUnitMajorVersion", str(major_version))
2047
-
2048
- @GeneralUtilities.check_arguments
2049
- def set_constant_for_description(self, codeunit_folder: str) -> None:
2050
- self.assert_is_codeunit_folder(codeunit_folder)
2051
- codeunit_name: str = os.path.basename(codeunit_folder)
2052
- codeunit_description: str = self.get_codeunit_description(f"{codeunit_folder}/{codeunit_name}.codeunit.xml")
2053
- self.set_constant(codeunit_folder, "CodeUnitDescription", codeunit_description)
2054
-
2055
- @GeneralUtilities.check_arguments
2056
- def set_constant(self, codeunit_folder: str, constantname: str, constant_value: str, documentationsummary: str = None, constants_valuefile: str = None) -> None:
2057
- self.assert_is_codeunit_folder(codeunit_folder)
2058
- if documentationsummary is None:
2059
- documentationsummary = GeneralUtilities.empty_string
2060
- constants_folder = os.path.join(codeunit_folder, "Other", "Resources", "Constants")
2061
- GeneralUtilities.ensure_directory_exists(constants_folder)
2062
- constants_metafile = os.path.join(constants_folder, f"{constantname}.constant.xml")
2063
- if constants_valuefile is None:
2064
- constants_valuefile_folder = constants_folder
2065
- constants_valuefile_name = f"{constantname}.value.txt"
2066
- constants_valuefiler_reference = f"./{constants_valuefile_name}"
2067
- else:
2068
- constants_valuefile_folder = os.path.dirname(constants_valuefile)
2069
- constants_valuefile_name = os.path.basename(constants_valuefile)
2070
- constants_valuefiler_reference = os.path.join(constants_valuefile_folder, constants_valuefile_name)
2071
-
2072
- # TODO implement usage of self.reference_latest_version_of_xsd_when_generating_xml
2073
- GeneralUtilities.write_text_to_file(constants_metafile, f"""<?xml version="1.0" encoding="UTF-8" ?>
2074
- <cps:constant xmlns:cps="https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure" constantspecificationversion="1.1.0"
2075
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/raw/main/Conventions/RepositoryStructure/CommonProjectStructure/constant.xsd">
2076
- <cps:name>{constantname}</cps:name>
2077
- <cps:documentationsummary>{documentationsummary}</cps:documentationsummary>
2078
- <cps:path>{constants_valuefiler_reference}</cps:path>
2079
- </cps:constant>""")
2080
- # TODO validate generated xml against xsd
2081
- GeneralUtilities.write_text_to_file(os.path.join(constants_valuefile_folder, constants_valuefile_name), constant_value)
2082
-
2083
- @GeneralUtilities.check_arguments
2084
- def get_constant_value(self, source_codeunit_folder: str, constant_name: str) -> str:
2085
- self.assert_is_codeunit_folder(source_codeunit_folder)
2086
- value_file_relative = self.__get_constant_helper(source_codeunit_folder, constant_name, "path")
2087
- value_file = GeneralUtilities.resolve_relative_path(value_file_relative, os.path.join(source_codeunit_folder, "Other", "Resources", "Constants"))
2088
- return GeneralUtilities.read_text_from_file(value_file)
2089
-
2090
- @GeneralUtilities.check_arguments
2091
- def get_constant_documentation(self, source_codeunit_folder: str, constant_name: str) -> str:
2092
- self.assert_is_codeunit_folder(source_codeunit_folder)
2093
- return self.__get_constant_helper(source_codeunit_folder, constant_name, "documentationsummary")
2094
-
2095
- @GeneralUtilities.check_arguments
2096
- def __get_constant_helper(self, source_codeunit_folder: str, constant_name: str, propertyname: str) -> str:
2097
- self.assert_is_codeunit_folder(source_codeunit_folder)
2098
- root: etree._ElementTree = etree.parse(os.path.join(source_codeunit_folder, "Other", "Resources", "Constants", f"{constant_name}.constant.xml"))
2099
- results = root.xpath(f'//cps:{propertyname}/text()', namespaces={
2100
- 'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure'
2101
- })
2102
- length = len(results)
2103
- if (length == 0):
2104
- return ""
2105
- elif length == 1:
2106
- return results[0]
2107
- else:
2108
- raise ValueError("Too many results found.")
2109
-
2110
- @GeneralUtilities.check_arguments
2111
- def copy_development_certificate_to_default_development_directory(self, codeunit_folder: str, build_environment: str, domain: str = None, certificate_resource_name: str = "DevelopmentCertificate") -> None:
2112
- self.assert_is_codeunit_folder(codeunit_folder)
2113
- if build_environment != "Productive":
2114
- codeunit_name: str = os.path.basename(codeunit_folder)
2115
- if domain is None:
2116
- domain = f"{codeunit_name}.test.local".lower()
2117
-
2118
- src_folder = os.path.join(codeunit_folder, "Other", "Resources", certificate_resource_name)
2119
- src_file_pfx = os.path.join(src_folder, f"{codeunit_name}{certificate_resource_name}.pfx")
2120
- src_file_psw = os.path.join(src_folder, f"{codeunit_name}{certificate_resource_name}.password")
2121
-
2122
- trg_folder = os.path.join(codeunit_folder, "Other", "Workspace", "Configuration", "Certificates")
2123
- trg_file_pfx = os.path.join(trg_folder, f"{domain}.pfx")
2124
- trg_file_psw = os.path.join(trg_folder, f"{domain}.password")
2125
-
2126
- GeneralUtilities.assert_file_exists(src_file_pfx)
2127
- GeneralUtilities.assert_file_exists(src_file_psw)
2128
- GeneralUtilities.ensure_file_does_not_exist(trg_file_pfx)
2129
- GeneralUtilities.ensure_file_does_not_exist(trg_file_psw)
2130
-
2131
- GeneralUtilities.ensure_directory_exists(trg_folder)
2132
-
2133
- GeneralUtilities.ensure_directory_exists(trg_folder)
2134
- shutil.copyfile(src_file_pfx, trg_file_pfx)
2135
- shutil.copyfile(src_file_psw, trg_file_psw)
2136
-
2137
- @GeneralUtilities.check_arguments
2138
- def set_constants_for_certificate_public_information(self, codeunit_folder: str, source_constant_name: str = "DevelopmentCertificate", domain: str = None) -> None:
2139
- """Expects a certificate-resource and generates a constant for its public information"""
2140
- self.assert_is_codeunit_folder(codeunit_folder)
2141
- certificate_file = os.path.join(codeunit_folder, "Other", "Resources", source_constant_name, f"{source_constant_name}.crt")
2142
- with open(certificate_file, encoding="utf-8") as text_wrapper:
2143
- certificate = crypto.load_certificate(crypto.FILETYPE_PEM, text_wrapper.read())
2144
- certificate_publickey = crypto.dump_publickey(crypto.FILETYPE_PEM, certificate.get_pubkey()).decode("utf-8")
2145
- self.set_constant(codeunit_folder, source_constant_name+"PublicKey", certificate_publickey)
2146
-
2147
- @GeneralUtilities.check_arguments
2148
- def set_constants_for_certificate_private_information(self, codeunit_folder: str) -> None:
2149
- """Expects a certificate-resource and generates a constant for its sensitive information in hex-format"""
2150
- self.assert_is_codeunit_folder(codeunit_folder)
2151
- codeunit_name = os.path.basename(codeunit_folder)
2152
- resource_name: str = "DevelopmentCertificate"
2153
- filename: str = codeunit_name+"DevelopmentCertificate"
2154
- self.generate_constant_from_resource_by_filename(codeunit_folder, resource_name, f"{filename}.pfx", "PFX")
2155
- self.generate_constant_from_resource_by_filename(codeunit_folder, resource_name, f"{filename}.password", "Password")
2156
-
2157
- @GeneralUtilities.check_arguments
2158
- def generate_constant_from_resource_by_filename(self, codeunit_folder: str, resource_name: str, filename: str, constant_name: str) -> None:
2159
- self.assert_is_codeunit_folder(codeunit_folder)
2160
- certificate_resource_folder = GeneralUtilities.resolve_relative_path(f"Other/Resources/{resource_name}", codeunit_folder)
2161
- resource_file = os.path.join(certificate_resource_folder, filename)
2162
- resource_file_content = GeneralUtilities.read_binary_from_file(resource_file)
2163
- resource_file_as_hex = resource_file_content.hex()
2164
- self.set_constant(codeunit_folder, f"{resource_name}{constant_name}Hex", resource_file_as_hex)
2165
-
2166
- @GeneralUtilities.check_arguments
2167
- def generate_constant_from_resource_by_extension(self, codeunit_folder: str, resource_name: str, extension: str, constant_name: str) -> None:
2168
- self.assert_is_codeunit_folder(codeunit_folder)
2169
- certificate_resource_folder = GeneralUtilities.resolve_relative_path(f"Other/Resources/{resource_name}", codeunit_folder)
2170
- resource_file = self.__sc.find_file_by_extension(certificate_resource_folder, extension)
2171
- resource_file_content = GeneralUtilities.read_binary_from_file(resource_file)
2172
- resource_file_as_hex = resource_file_content.hex()
2173
- self.set_constant(codeunit_folder, f"{resource_name}{constant_name}Hex", resource_file_as_hex)
2174
-
2175
- @GeneralUtilities.check_arguments
2176
- def copy_constant_from_dependent_codeunit(self, codeunit_folder: str, constant_name: str, source_codeunit_name: str) -> None:
2177
- self.assert_is_codeunit_folder(codeunit_folder)
2178
- source_codeunit_folder: str = GeneralUtilities.resolve_relative_path(f"../{source_codeunit_name}", codeunit_folder)
2179
- value = self.get_constant_value(source_codeunit_folder, constant_name)
2180
- documentation = self.get_constant_documentation(source_codeunit_folder, constant_name)
2181
- self.set_constant(codeunit_folder, constant_name, value, documentation)
2182
-
2183
- @GeneralUtilities.check_arguments
2184
- def copy_resources_from_dependent_codeunit(self, codeunit_folder: str, resource_name: str, source_codeunit_name: str) -> None:
2185
- self.assert_is_codeunit_folder(codeunit_folder)
2186
- source_folder: str = GeneralUtilities.resolve_relative_path(f"../{source_codeunit_name}/Other/Resources/{resource_name}", codeunit_folder)
2187
- target_folder: str = GeneralUtilities.resolve_relative_path(f"Other/Resources/{resource_name}", codeunit_folder)
2188
- GeneralUtilities.ensure_directory_does_not_exist(target_folder)
2189
- shutil.copytree(source_folder, target_folder)
2190
-
2191
- @GeneralUtilities.check_arguments
2192
- def copy_resources_from_global_project_resources(self, codeunit_folder: str, resource_name: str) -> None:
2193
- self.assert_is_codeunit_folder(codeunit_folder)
2194
- source_folder: str = GeneralUtilities.resolve_relative_path(f"../Other/Resources/{resource_name}", codeunit_folder)
2195
- target_folder: str = GeneralUtilities.resolve_relative_path(f"Other/Resources/{resource_name}", codeunit_folder)
2196
- GeneralUtilities.ensure_directory_does_not_exist(target_folder)
2197
- shutil.copytree(source_folder, target_folder)
2198
-
2199
- @GeneralUtilities.check_arguments
2200
- def generate_openapi_file(self, buildscript_file: str, runtime: str, verbosity: int, commandline_arguments: list[str], swagger_document_name: str = "APISpecification") -> None:
2201
- GeneralUtilities.write_message_to_stdout("Generate OpenAPI-specification-file...")
2202
- codeunitname = os.path.basename(str(Path(os.path.dirname(buildscript_file)).parent.parent.absolute()))
2203
- repository_folder = str(Path(os.path.dirname(buildscript_file)).parent.parent.parent.absolute())
2204
- codeunit_folder = os.path.join(repository_folder, codeunitname)
2205
- self.assert_is_codeunit_folder(codeunit_folder)
2206
- artifacts_folder = os.path.join(codeunit_folder, "Other", "Artifacts")
2207
- GeneralUtilities.ensure_directory_exists(os.path.join(artifacts_folder, "APISpecification"))
2208
- verbosity = self.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
2209
- codeunit_version = self.get_version_of_codeunit_folder(codeunit_folder)
2210
-
2211
- versioned_api_spec_file = f"APISpecification/{codeunitname}.v{codeunit_version}.api.json"
2212
- self.__sc.run_program("swagger", f"tofile --output {versioned_api_spec_file} BuildResult_DotNet_{runtime}/{codeunitname}.dll {swagger_document_name}", artifacts_folder, verbosity=verbosity)
2213
- api_file: str = os.path.join(artifacts_folder, versioned_api_spec_file)
2214
- shutil.copyfile(api_file, os.path.join(artifacts_folder, f"APISpecification/{codeunitname}.latest.api.json"))
2215
-
2216
- resources_folder = os.path.join(codeunit_folder, "Other", "Resources")
2217
- GeneralUtilities.ensure_directory_exists(resources_folder)
2218
- resources_apispec_folder = os.path.join(resources_folder, "APISpecification")
2219
- GeneralUtilities.ensure_directory_exists(resources_apispec_folder)
2220
- resource_target_file = os.path.join(resources_apispec_folder, f"{codeunitname}.api.json")
2221
- GeneralUtilities.ensure_file_does_not_exist(resource_target_file)
2222
- shutil.copyfile(api_file, resource_target_file)
2223
-
2224
- with open(api_file, encoding="utf-8") as api_file_content:
2225
- reloaded_json = json.load(api_file_content)
2226
-
2227
- yamlfile1: str = str(os.path.join(artifacts_folder, f"APISpecification/{codeunitname}.v{codeunit_version}.api.yaml"))
2228
- GeneralUtilities.ensure_file_does_not_exist(yamlfile1)
2229
- GeneralUtilities.ensure_file_exists(yamlfile1)
2230
- with open(yamlfile1, "w+", encoding="utf-8") as yamlfile:
2231
- yaml.dump(reloaded_json, yamlfile, allow_unicode=True)
2232
-
2233
- yamlfile2: str = str(os.path.join(artifacts_folder, f"APISpecification/{codeunitname}.latest.api.yaml"))
2234
- GeneralUtilities.ensure_file_does_not_exist(yamlfile2)
2235
- shutil.copyfile(yamlfile1, yamlfile2)
2236
-
2237
- yamlfile3: str = str(os.path.join(resources_apispec_folder, f"{codeunitname}.api.yaml"))
2238
- GeneralUtilities.ensure_file_does_not_exist(yamlfile3)
2239
- shutil.copyfile(yamlfile1, yamlfile3)
2240
-
2241
- @GeneralUtilities.check_arguments
2242
- def get_latest_version_of_openapigenerator(self) -> None:
2243
- github_api_releases_link = "https://api.github.com/repos/OpenAPITools/openapi-generator/releases"
2244
- with urllib.request.urlopen(github_api_releases_link) as release_information_url:
2245
- latest_release_infos = json.load(release_information_url)[0]
2246
- latest_version = latest_release_infos["tag_name"][1:]
2247
- return latest_version
2248
-
2249
- @GeneralUtilities.check_arguments
2250
- def set_version_of_openapigenerator_by_update_dependencies_file(self, update_dependencies_script_file: str, used_version: str = None) -> None:
2251
- codeunit_folder: str = GeneralUtilities.resolve_relative_path("../..", update_dependencies_script_file)
2252
- self.set_version_of_openapigenerator(codeunit_folder, used_version)
2253
-
2254
- @GeneralUtilities.check_arguments
2255
- def set_version_of_openapigenerator(self, codeunit_folder: str, used_version: str = None) -> None:
2256
- target_folder: str = os.path.join(codeunit_folder, "Other", "Resources", "Dependencies", "OpenAPIGenerator")
2257
- version_file = os.path.join(target_folder, "Version.txt")
2258
- if used_version is None:
2259
- used_version = self.get_latest_version_of_openapigenerator()
2260
- GeneralUtilities.ensure_directory_exists(target_folder)
2261
- GeneralUtilities.ensure_file_exists(version_file)
2262
- GeneralUtilities.write_text_to_file(version_file, used_version)
2263
-
2264
- @GeneralUtilities.check_arguments
2265
- def ensure_openapigenerator_is_available(self, codeunit_folder: str) -> None:
2266
- self.assert_is_codeunit_folder(codeunit_folder)
2267
- openapigenerator_folder = os.path.join(codeunit_folder, "Other", "Resources", "OpenAPIGenerator")
2268
- internet_connection_is_available = GeneralUtilities.internet_connection_is_available()
2269
- filename = "open-api-generator.jar"
2270
- jar_file = f"{openapigenerator_folder}/{filename}"
2271
- jar_file_exists = os.path.isfile(jar_file)
2272
- if internet_connection_is_available: # Load/Update
2273
- version_file = os.path.join(codeunit_folder, "Other", "Resources", "Dependencies", "OpenAPIGenerator", "Version.txt")
2274
- used_version = GeneralUtilities.read_text_from_file(version_file)
2275
- download_link = f"https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/{used_version}/openapi-generator-cli-{used_version}.jar"
2276
- GeneralUtilities.ensure_directory_does_not_exist(openapigenerator_folder)
2277
- GeneralUtilities.ensure_directory_exists(openapigenerator_folder)
2278
- urllib.request.urlretrieve(download_link, jar_file)
2279
- else:
2280
- if jar_file_exists:
2281
- GeneralUtilities.write_message_to_stdout("Warning: Can not check for updates of OpenAPIGenerator due to missing internet-connection.")
2282
- else:
2283
- raise ValueError("Can not download OpenAPIGenerator.")
2284
-
2285
- @GeneralUtilities.check_arguments
2286
- def generate_api_client_from_dependent_codeunit_in_angular(self, file: str, name_of_api_providing_codeunit: str, generated_program_part_name: str) -> None:
2287
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", file)
2288
- target_subfolder_in_codeunit = f"src/app/generated/{generated_program_part_name}"
2289
- language = "typescript-angular"
2290
- self.ensure_openapigenerator_is_available(codeunit_folder)
2291
- openapigenerator_jar_file = os.path.join(codeunit_folder, "Other", "Resources", "OpenAPIGenerator", "open-api-generator.jar")
2292
- openapi_spec_file = os.path.join(codeunit_folder, "Other", "Resources", "DependentCodeUnits", name_of_api_providing_codeunit, "APISpecification", f"{name_of_api_providing_codeunit}.latest.api.json")
2293
- target_folder = os.path.join(codeunit_folder, target_subfolder_in_codeunit)
2294
- GeneralUtilities.ensure_folder_exists_and_is_empty(target_folder)
2295
- self.__sc.run_program("java", f'-jar {openapigenerator_jar_file} generate -i {openapi_spec_file} -g {language} -o {target_folder} --global-property supportingFiles --global-property models --global-property apis', codeunit_folder)
2296
-
2297
- @GeneralUtilities.check_arguments
2298
- def generate_api_client_from_dependent_codeunit_in_dotnet(self, file: str, name_of_api_providing_codeunit: str, base_namespace: str) -> None:
2299
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", file)
2300
- codeunit_name = os.path.basename(codeunit_folder)
2301
- client_subpath = f"{codeunit_name}/APIClients/{name_of_api_providing_codeunit}"
2302
- namespace = f"{base_namespace}.APIClients.{name_of_api_providing_codeunit}"
2303
- target_subfolder_in_codeunit = client_subpath
2304
- language = "csharp"
2305
- additional_properties = f"--additional-properties packageName={namespace}"
2306
-
2307
- codeunit_folder = GeneralUtilities.resolve_relative_path("../..", file)
2308
- self.ensure_openapigenerator_is_available(codeunit_folder)
2309
- openapigenerator_jar_file = os.path.join(codeunit_folder, "Other", "Resources", "OpenAPIGenerator", "open-api-generator.jar")
2310
- openapi_spec_file = os.path.join(codeunit_folder, "Other", "Resources", "DependentCodeUnits", name_of_api_providing_codeunit, "APISpecification", f"{name_of_api_providing_codeunit}.latest.api.json")
2311
- target_folder = os.path.join(codeunit_folder, target_subfolder_in_codeunit)
2312
- GeneralUtilities.ensure_directory_exists(target_folder)
2313
- self.__sc.run_program("java", f'-jar {openapigenerator_jar_file} generate -i {openapi_spec_file} -g {language} -o {target_folder} --global-property supportingFiles --global-property models --global-property apis {additional_properties}', codeunit_folder)
2314
-
2315
- # move docs to correct folder
2316
- target_folder_docs = os.path.join(target_folder, "docs")
2317
- target_folder_docs_correct = os.path.join(codeunit_folder, "Other", "Reference", "ReferenceContent", f"{name_of_api_providing_codeunit}-API")
2318
- GeneralUtilities.ensure_directory_does_not_exist(target_folder_docs_correct)
2319
- GeneralUtilities.ensure_directory_exists(target_folder_docs_correct)
2320
- GeneralUtilities.move_content_of_folder(target_folder_docs, target_folder_docs_correct)
2321
- GeneralUtilities.ensure_directory_does_not_exist(target_folder_docs)
2322
-
2323
- code_folders = GeneralUtilities.get_direct_folders_of_folder(os.path.join(target_folder, "src"))
2324
-
2325
- # remove test-folder
2326
- tests_folder = [x for x in code_folders if x.endswith(".Test")][0]
2327
- GeneralUtilities.ensure_directory_does_not_exist(tests_folder)
2328
-
2329
- # move source to correct folder
2330
- src_folder = [x for x in code_folders if not x.endswith(".Test")][0]
2331
- target_folder_src = GeneralUtilities.resolve_relative_path("../..", src_folder)
2332
-
2333
- for targetfile in GeneralUtilities.get_direct_files_of_folder(target_folder_src):
2334
- GeneralUtilities.ensure_file_does_not_exist(targetfile)
2335
- for folder in GeneralUtilities.get_direct_folders_of_folder(target_folder_src):
2336
- f = folder.replace("\\", "/")
2337
- if not f.endswith("/.openapi-generator") and not f.endswith("/src"):
2338
- GeneralUtilities.ensure_directory_does_not_exist(f)
2339
- GeneralUtilities.ensure_directory_exists(target_folder_src)
2340
- GeneralUtilities.move_content_of_folder(src_folder, target_folder_src)
2341
- GeneralUtilities.ensure_directory_does_not_exist(src_folder)
2342
- for targetfile in GeneralUtilities.get_direct_files_of_folder(target_folder_src):
2343
- GeneralUtilities.ensure_file_does_not_exist(targetfile)
2344
-
2345
- @GeneralUtilities.check_arguments
2346
- def replace_version_in_packagejson_file(self, packagejson_file: str, codeunit_version: str) -> None:
2347
- encoding = "utf-8"
2348
- with open(packagejson_file, encoding=encoding) as f:
2349
- data = json.load(f)
2350
- data['version'] = codeunit_version
2351
- with open(packagejson_file, 'w', encoding=encoding) as f:
2352
- json.dump(data, f, indent=2)
2353
-
2354
- @GeneralUtilities.check_arguments
2355
- def build_dependent_code_units(self, repo_folder: str, codeunit_name: str, verbosity: int, target_environmenttype: str, additional_arguments_file: str, commandlinearguments: list[str]) -> None:
2356
- verbosity = self.get_verbosity_from_commandline_arguments(commandlinearguments, verbosity)
2357
- codeunit_file = os.path.join(repo_folder, codeunit_name, codeunit_name + ".codeunit.xml")
2358
- dependent_codeunits = self.get_dependent_code_units(codeunit_file)
2359
- dependent_codeunits_folder = os.path.join(repo_folder, codeunit_name, "Other", "Resources", "DependentCodeUnits")
2360
- GeneralUtilities.ensure_directory_does_not_exist(dependent_codeunits_folder)
2361
- if 0 < len(dependent_codeunits):
2362
- GeneralUtilities.write_message_to_stdout(f"Start building dependent codeunits for codeunit {codeunit_name}.")
2363
- for dependent_codeunit in dependent_codeunits:
2364
- self.__build_codeunit(os.path.join(repo_folder, dependent_codeunit), verbosity, target_environmenttype, additional_arguments_file, False, False, commandlinearguments)
2365
- if 0 < len(dependent_codeunits):
2366
- GeneralUtilities.write_message_to_stdout(f"Finished building dependent codeunits for codeunit {codeunit_name}.")
2367
-
2368
- @GeneralUtilities.check_arguments
2369
- def copy_artifacts_from_dependent_code_units(self, repo_folder: str, codeunit_name: str) -> None:
2370
- codeunit_file = os.path.join(repo_folder, codeunit_name, codeunit_name + ".codeunit.xml")
2371
- dependent_codeunits = self.get_dependent_code_units(codeunit_file)
2372
- if len(dependent_codeunits) > 0:
2373
- GeneralUtilities.write_message_to_stdout(f"Get dependent artifacts for codeunit {codeunit_name}.")
2374
- dependent_codeunits_folder = os.path.join(repo_folder, codeunit_name, "Other", "Resources", "DependentCodeUnits")
2375
- GeneralUtilities.ensure_directory_does_not_exist(dependent_codeunits_folder)
2376
- for dependent_codeunit in dependent_codeunits:
2377
- target_folder = os.path.join(dependent_codeunits_folder, dependent_codeunit)
2378
- GeneralUtilities.ensure_directory_does_not_exist(target_folder)
2379
- other_folder = os.path.join(repo_folder, dependent_codeunit, "Other")
2380
- artifacts_folder = os.path.join(other_folder, "Artifacts")
2381
- shutil.copytree(artifacts_folder, target_folder)
2382
-
2383
- @GeneralUtilities.check_arguments
2384
- def add_github_release(self, productname: str, projectversion: str, build_artifacts_folder: str, github_username: str, repository_folder: str, commandline_arguments: list[str], additional_attached_files: list[str]) -> None:
2385
- self.__sc.assert_is_git_repository(repository_folder)
2386
- GeneralUtilities.write_message_to_stdout(f"Create GitHub-release for {productname}...")
2387
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, 1)
2388
- github_repo = f"{github_username}/{productname}"
2389
- artifact_files = []
2390
- codeunits = self.get_codeunits(repository_folder)
2391
- for codeunit in codeunits:
2392
- artifact_files.append(self.__sc.find_file_by_extension(f"{build_artifacts_folder}\\{productname}\\{projectversion}\\{codeunit}", "Productive.Artifacts.zip"))
2393
- if additional_attached_files is not None:
2394
- for additional_attached_file in additional_attached_files:
2395
- artifact_files.append(additional_attached_file)
2396
- changelog_file = os.path.join(repository_folder, "Other", "Resources", "Changelog", f"v{projectversion}.md")
2397
- self.__sc.run_program_argsasarray("gh", ["release", "create", f"v{projectversion}", "--repo", github_repo, "--notes-file", changelog_file, "--title", f"Release v{projectversion}"]+artifact_files, verbosity=verbosity)
2398
-
2399
- @GeneralUtilities.check_arguments
2400
- def get_dependencies_which_are_ignored_from_updates(self, codeunit_folder: str, print_warnings_for_ignored_dependencies: bool) -> list[str]:
2401
- self.assert_is_codeunit_folder(codeunit_folder)
2402
- namespaces = {'cps': 'https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure', 'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
2403
- codeunit_name = os.path.basename(codeunit_folder)
2404
- codeunit_file = os.path.join(codeunit_folder, f"{codeunit_name}.codeunit.xml")
2405
- root: etree._ElementTree = etree.parse(codeunit_file)
2406
- ignoreddependencies = root.xpath('//cps:codeunit/cps:properties/cps:updatesettings/cps:ignoreddependencies/cps:ignoreddependency', namespaces=namespaces)
2407
- result = [x.text.replace("\\n", GeneralUtilities.empty_string).replace("\\r", GeneralUtilities.empty_string).replace("\n", GeneralUtilities.empty_string).replace("\r", GeneralUtilities.empty_string).strip() for x in ignoreddependencies]
2408
- if print_warnings_for_ignored_dependencies and len(result) > 0:
2409
- GeneralUtilities.write_message_to_stderr(f"Warning: Codeunit {codeunit_name} contains the following dependencies which will are ignoed for automatic updates: "+', '.join(result))
2410
- return result
2411
-
2412
- @GeneralUtilities.check_arguments
2413
- def update_dependencies_of_typical_flutter_codeunit(self, update_script_file: str, verbosity: int, cmd_args: list[str]) -> None:
2414
- codeunit_folder = GeneralUtilities.resolve_relative_path("..", os.path.dirname(update_script_file))
2415
- ignored_dependencies = self.get_dependencies_which_are_ignored_from_updates(codeunit_folder, True)
2416
- # TODO implement
2417
-
2418
- @GeneralUtilities.check_arguments
2419
- def update_dependencies_of_typical_python_repository_requirements(self, repository_folder: str, verbosity: int, cmd_args: list[str]) -> None:
2420
- verbosity = self.get_verbosity_from_commandline_arguments(cmd_args, verbosity)
2421
-
2422
- development_requirements_file = os.path.join(repository_folder, "Other", "requirements.txt")
2423
- if (os.path.isfile(development_requirements_file)):
2424
- self.__sc.update_dependencies_of_python_in_requirementstxt_file(development_requirements_file, [], verbosity)
2425
-
2426
- @GeneralUtilities.check_arguments
2427
- def update_dependencies_of_typical_python_codeunit(self, update_script_file: str, verbosity: int, cmd_args: list[str]) -> None:
2428
- codeunit_folder = GeneralUtilities.resolve_relative_path("..", os.path.dirname(update_script_file))
2429
- ignored_dependencies = self.get_dependencies_which_are_ignored_from_updates(codeunit_folder, True)
2430
- # TODO consider ignored_dependencies
2431
- verbosity = self.get_verbosity_from_commandline_arguments(cmd_args, verbosity)
2432
-
2433
- setup_cfg = os.path.join(codeunit_folder, "setup.cfg")
2434
- if (os.path.isfile(setup_cfg)):
2435
- self.__sc.update_dependencies_of_python_in_setupcfg_file(setup_cfg, ignored_dependencies, verbosity)
2436
-
2437
- development_requirements_file = os.path.join(codeunit_folder, "requirements.txt") # required for codeunits which contain python-code which need third-party dependencies
2438
- if (os.path.isfile(development_requirements_file)):
2439
- self.__sc.update_dependencies_of_python_in_requirementstxt_file(development_requirements_file, ignored_dependencies, verbosity)
2440
-
2441
- development_requirements_file2 = os.path.join(codeunit_folder, "Other", "requirements.txt") # required for codeunits which contain python-scripts which needs third-party dependencies
2442
- if (os.path.isfile(development_requirements_file2)):
2443
- self.__sc.update_dependencies_of_python_in_requirementstxt_file(development_requirements_file2, ignored_dependencies, verbosity)
2444
-
2445
- @GeneralUtilities.check_arguments
2446
- def update_dependencies_of_typical_dotnet_codeunit(self, update_script_file: str, verbosity: int, cmd_args: list[str]) -> None:
2447
- codeunit_folder = GeneralUtilities.resolve_relative_path("..", os.path.dirname(update_script_file))
2448
- ignored_dependencies = self.get_dependencies_which_are_ignored_from_updates(codeunit_folder, True)
2449
- verbosity = self.get_verbosity_from_commandline_arguments(cmd_args, verbosity)
2450
- codeunit_name = os.path.basename(codeunit_folder)
2451
-
2452
- build_folder = os.path.join(codeunit_folder, "Other", "Build")
2453
- self.__sc.run_program("python", "Build.py", build_folder, verbosity)
2454
-
2455
- test_csproj_file = os.path.join(codeunit_folder, f"{codeunit_name}Tests", f"{codeunit_name}Tests.csproj")
2456
- self.__sc.update_dependencies_of_dotnet_project(test_csproj_file, verbosity, ignored_dependencies)
2457
- csproj_file = os.path.join(codeunit_folder, codeunit_name, f"{codeunit_name}.csproj")
2458
- self.__sc.update_dependencies_of_dotnet_project(csproj_file, verbosity, ignored_dependencies)
2459
-
2460
- @GeneralUtilities.check_arguments
2461
- def update_dependencies_of_package_json(self, folder: str, verbosity: int, cmd_args: list[str]) -> None:
2462
- if self.is_codeunit_folder(folder):
2463
- ignored_dependencies = self.get_dependencies_which_are_ignored_from_updates(folder, True)
2464
- else:
2465
- ignored_dependencies = []
2466
- # TODO consider ignored_dependencies
2467
- result = self.run_with_epew("npm", "outdated", folder, verbosity, throw_exception_if_exitcode_is_not_zero=False)
2468
- if result[0] == 0:
2469
- return # all dependencies up to date
2470
- elif result[0] == 1:
2471
- package_json_content = None
2472
- package_json_file = f"{folder}/package.json"
2473
- with open(package_json_file, "r", encoding="utf-8") as package_json_file_object:
2474
- package_json_content = json.load(package_json_file_object)
2475
- lines = GeneralUtilities.string_to_lines(result[1])[1:][:-1]
2476
- for line in lines:
2477
- normalized_line_splitted = ' '.join(line.split()).split(" ")
2478
- package = normalized_line_splitted[0]
2479
- latest_version = normalized_line_splitted[3]
2480
- if package in package_json_content["dependencies"]:
2481
- package_json_content["dependencies"][package] = latest_version
2482
- if package in package_json_content["devDependencies"]:
2483
- package_json_content["devDependencies"][package] = latest_version
2484
- with open(package_json_file, "w", encoding="utf-8") as package_json_file_object:
2485
- json.dump(package_json_content, package_json_file_object, indent=4)
2486
- self.do_npm_install(folder, True, verbosity)
2487
- else:
2488
- GeneralUtilities.write_message_to_stderr("Update dependencies resulted in an error.")
2489
-
2490
- @GeneralUtilities.check_arguments
2491
- def generate_tasksfile_from_workspace_file(self, repository_folder: str, append_cli_args_at_end: bool = False) -> None:
2492
- """This function works platform-independent also for non-local-executions if the ScriptCollection commandline-commands are available as global command on the target-system."""
2493
- if self.__sc.program_runner.will_be_executed_locally(): # works only locally, but much more performant than always running an external program
2494
- self.__sc.assert_is_git_repository(repository_folder)
2495
- workspace_file: str = self.__sc.find_file_by_extension(repository_folder, "code-workspace")
2496
- task_file: str = repository_folder + "/Taskfile.yml"
2497
- lines: list[str] = ["version: '3'", GeneralUtilities.empty_string, "tasks:", GeneralUtilities.empty_string]
2498
- workspace_file_content: str = self.__sc.get_file_content(workspace_file)
2499
- jsoncontent = json.loads(workspace_file_content)
2500
- tasks = jsoncontent["tasks"]["tasks"]
2501
- tasks.sort(key=lambda x: x["label"].split("/")[-1], reverse=False) # sort by the label of the task
2502
- for task in tasks:
2503
- if task["type"] == "shell":
2504
-
2505
- description: str = task["label"]
2506
- name: str = GeneralUtilities.to_pascal_case(description)
2507
- command = task["command"]
2508
- relative_script_file = task["command"]
2509
-
2510
- relative_script_file = "."
2511
- cwd: str = None
2512
- if "options" in task:
2513
- options = task["options"]
2514
- if "cwd" in options:
2515
- cwd = options["cwd"]
2516
- cwd = cwd.replace("${workspaceFolder}", ".")
2517
- cwd = cwd.replace("\\", "\\\\").replace('"', '\\"') # escape backslashes and double quotes for YAML
2518
- relative_script_file = cwd
2519
- if len(relative_script_file) == 0:
2520
- relative_script_file = "."
2521
-
2522
- command_with_args = command
2523
- if "args" in task:
2524
- args = task["args"]
2525
- if len(args) > 1:
2526
- command_with_args = f"{command_with_args} {' '.join(args)}"
2527
-
2528
- if "description" in task:
2529
- additional_description = task["description"]
2530
- description = f"{description} ({additional_description})"
2531
-
2532
- if append_cli_args_at_end:
2533
- command_with_args = f"{command_with_args} {{{{.CLI_ARGS}}}}"
2534
-
2535
- description_literal = description.replace("\\", "\\\\").replace('"', '\\"') # escape backslashes and double quotes for YAML
2536
- command_with_args = command_with_args.replace("\\", "\\\\").replace('"', '\\"') # escape backslashes and double quotes for YAML
2537
-
2538
- lines.append(f" {name}:")
2539
- lines.append(f' desc: "{description_literal}"')
2540
- lines.append(' silent: true')
2541
- if cwd is not None:
2542
- lines.append(f' dir: "{cwd}"')
2543
- lines.append(" cmds:")
2544
- lines.append(f' - "{command_with_args}"')
2545
- lines.append(' aliases:')
2546
- lines.append(f' - {name.lower()}')
2547
- if "aliases" in task:
2548
- aliases = task["aliases"]
2549
- for alias in aliases:
2550
- lines.append(f' - {alias}')
2551
- lines.append(GeneralUtilities.empty_string)
2552
-
2553
- self.__sc.set_file_content(task_file, "\n".join(lines))
2554
- else:
2555
- self.__sc.run_program("scgeneratetasksfilefromworkspacefile", f"--repositoryfolder {repository_folder}")
2556
-
2557
- @GeneralUtilities.check_arguments
2558
- def start_local_test_service(self, file: str):
2559
- example_folder = os.path.dirname(file)
2560
- docker_compose_file = os.path.join(example_folder, "docker-compose.yml")
2561
- for service in self.__sc.get_services_from_yaml_file(docker_compose_file):
2562
- self.__sc.kill_docker_container(service)
2563
- example_name = os.path.basename(example_folder)
2564
- title = f"Test{example_name}"
2565
- self.__sc.run_program("docker", f"compose -p {title.lower()} up --detach", example_folder, title=title)
2566
-
2567
- @GeneralUtilities.check_arguments
2568
- def stop_local_test_service(self, file: str):
2569
- example_folder = os.path.dirname(file)
2570
- example_name = os.path.basename(example_folder)
2571
- title = f"Test{example_name}"
2572
- self.__sc.run_program("docker", f"compose -p {title.lower()} down", example_folder, title=title)
2573
-
2574
- @GeneralUtilities.check_arguments
2575
- def standardized_tasks_update_version_in_docker_examples(self, file, codeunit_version) -> None:
2576
- folder_of_current_file = os.path.dirname(file)
2577
- codeunit_folder = GeneralUtilities.resolve_relative_path("..", folder_of_current_file)
2578
- codeunit_name = os.path.basename(codeunit_folder)
2579
- codeunit_name_lower = codeunit_name.lower()
2580
- examples_folder = GeneralUtilities.resolve_relative_path("Other/Reference/ReferenceContent/Examples", codeunit_folder)
2581
- for example_folder in GeneralUtilities.get_direct_folders_of_folder(examples_folder):
2582
- docker_compose_file = os.path.join(example_folder, "docker-compose.yml")
2583
- if os.path.isfile(docker_compose_file):
2584
- filecontent = GeneralUtilities.read_text_from_file(docker_compose_file)
2585
- replaced = re.sub(f'image:\\s+{codeunit_name_lower}:\\d+\\.\\d+\\.\\d+', f"image: {codeunit_name_lower}:{codeunit_version}", filecontent)
2586
- GeneralUtilities.write_text_to_file(docker_compose_file, replaced)
2587
-
2588
- @GeneralUtilities.check_arguments
2589
- def start_dockerfile_example(self, current_file: str, verbosity: int, remove_old_container: bool, remove_volumes_folder: bool, commandline_arguments: list[str], env_file: str) -> None:
2590
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
2591
- folder = os.path.dirname(current_file)
2592
- example_name = os.path.basename(folder)
2593
- oci_image_artifacts_folder = GeneralUtilities.resolve_relative_path("../../../../Artifacts/BuildResult_OCIImage", folder)
2594
- image_filename = os.path.basename(self.__sc.find_file_by_extension(oci_image_artifacts_folder, "tar"))
2595
- codeunit_name = os.path.basename(GeneralUtilities.resolve_relative_path("../../../../..", folder))
2596
- if remove_old_container:
2597
- docker_compose_file = f"{folder}/docker-compose.yml"
2598
- container_names = []
2599
- lines = GeneralUtilities.read_lines_from_file(docker_compose_file)
2600
- for line in lines:
2601
- if match := re.search("container_name:\\s*'?([^']+)'?", line):
2602
- container_names.append(match.group(1))
2603
- GeneralUtilities.write_message_to_stdout(f"Ensure container of {docker_compose_file} do not exist...")
2604
- for container_name in container_names:
2605
- GeneralUtilities.write_message_to_stdout(f"Ensure container {container_name} does not exist...")
2606
- self.__sc.run_program("docker", f"container rm -f {container_name}", oci_image_artifacts_folder, verbosity=0, throw_exception_if_exitcode_is_not_zero=False)
2607
- if remove_volumes_folder:
2608
- volumes_folder = os.path.join(folder, "Volumes")
2609
- GeneralUtilities.write_message_to_stdout(f"Ensure volumes-folder '{volumes_folder}' does not exist...")
2610
- GeneralUtilities.ensure_directory_does_not_exist(volumes_folder)
2611
- GeneralUtilities.ensure_directory_exists(volumes_folder)
2612
- GeneralUtilities.write_message_to_stdout("Load docker-image...")
2613
- self.__sc.run_program("docker", f"load -i {image_filename}", oci_image_artifacts_folder, verbosity=verbosity)
2614
- docker_project_name = f"{codeunit_name}_{example_name}".lower()
2615
- GeneralUtilities.write_message_to_stdout("Start docker-container...")
2616
- argument = f"compose --project-name {docker_project_name}"
2617
- if env_file is not None:
2618
- argument = f"{argument} --env-file {env_file}"
2619
- argument = f"{argument} up --detach"
2620
- self.__sc.run_program("docker", argument, folder, verbosity=verbosity)
2621
-
2622
- @GeneralUtilities.check_arguments
2623
- def ensure_env_file_is_generated(self, current_file: str, env_file_name: str, env_values: dict[str, str]):
2624
- folder = os.path.dirname(current_file)
2625
- env_file = os.path.join(folder, env_file_name)
2626
- if not os.path.isfile(env_file):
2627
- lines = []
2628
- for key, value in env_values.items():
2629
- lines.append(f"{key}={value}")
2630
- GeneralUtilities.write_lines_to_file(env_file, lines)
2631
-
2632
- @GeneralUtilities.check_arguments
2633
- def stop_dockerfile_example(self, current_file: str, verbosity: int, remove_old_container: bool, remove_volumes_folder: bool, commandline_arguments: list[str]) -> None:
2634
- verbosity = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
2635
- folder = os.path.dirname(current_file)
2636
- example_name = os.path.basename(folder)
2637
- codeunit_name = os.path.basename(GeneralUtilities.resolve_relative_path("../../../../..", folder))
2638
- docker_project_name = f"{codeunit_name}_{example_name}".lower()
2639
- GeneralUtilities.write_message_to_stdout("Stop docker-container...")
2640
- self.__sc.run_program("docker", f"compose --project-name {docker_project_name} down", folder, verbosity=verbosity)
2641
-
2642
- @GeneralUtilities.check_arguments
2643
- def create_artifact_for_development_certificate(self, codeunit_folder: str):
2644
- self.assert_is_codeunit_folder(codeunit_folder)
2645
- ce_source_folder = GeneralUtilities.resolve_relative_path("Other/Resources/DevelopmentCertificate", codeunit_folder)
2646
- ca_source_folder = GeneralUtilities.resolve_relative_path("Other/Resources/CA", codeunit_folder)
2647
- ce_target_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts/DevelopmentCertificate", codeunit_folder)
2648
- ca_target_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts/CA", codeunit_folder)
2649
-
2650
- GeneralUtilities.ensure_directory_does_not_exist(ce_target_folder)
2651
- GeneralUtilities.ensure_directory_exists(ce_target_folder)
2652
- GeneralUtilities.copy_content_of_folder(ce_source_folder, ce_target_folder)
2653
- GeneralUtilities.ensure_directory_does_not_exist(ca_target_folder)
2654
- GeneralUtilities.ensure_directory_exists(ca_target_folder)
2655
- GeneralUtilities.copy_content_of_folder(ca_source_folder, ca_target_folder)
2656
-
2657
- @GeneralUtilities.check_arguments
2658
- def _internal_get_sorted_codeunits_by_dict(self, codeunits: dict[str, set[str]]) -> list[str]:
2659
- sorted_codeunits = {
2660
- node: sorted(codeunits[node])
2661
- for node in sorted(codeunits)
2662
- }
2663
-
2664
- ts = TopologicalSorter()
2665
- for node, deps in sorted_codeunits.items():
2666
- ts.add(node, *deps)
2667
-
2668
- result_typed = list(ts.static_order())
2669
- result = [str(item) for item in result_typed]
2670
- return result
2671
-
2672
- @GeneralUtilities.check_arguments
2673
- def get_project_name(self, repository_folder: str) -> str:
2674
- self.__sc.assert_is_git_repository(repository_folder)
2675
- for file in GeneralUtilities.get_direct_files_of_folder(repository_folder):
2676
- if file.endswith(".code-workspace"):
2677
- return Path(file).stem
2678
- raise ValueError(f'Project-name can not be calculated for repository "{repository_folder}"')
2679
-
2680
- def __check_target_environmenttype(self, target_environmenttype: str):
2681
- allowed_values = list(self.get_default_target_environmenttype_mapping().values())
2682
- if not (target_environmenttype in allowed_values):
2683
- raise ValueError(f"Invalid target-environmenttype: '{target_environmenttype}'")
2684
-
2685
- @GeneralUtilities.check_arguments
2686
- def build_codeunit(self, codeunit_folder: str, verbosity: int = 1, target_environmenttype: str = "QualityCheck", additional_arguments_file: str = None, is_pre_merge: bool = False, export_target_directory: str = None, assume_dependent_codeunits_are_already_built: bool = False, commandlinearguments: list[str] = []) -> None:
2687
- self.assert_is_codeunit_folder(codeunit_folder)
2688
- codeunit_name = os.path.basename(codeunit_folder)
2689
- repository_folder = os.path.dirname(codeunit_folder)
2690
- self.build_specific_codeunits(repository_folder, [codeunit_name], verbosity, target_environmenttype, additional_arguments_file, is_pre_merge, export_target_directory, assume_dependent_codeunits_are_already_built, commandlinearguments, False)
2691
-
2692
- @GeneralUtilities.check_arguments
2693
- def build_codeunitsC(self, repository_folder: str, image: str, verbosity: int = 1, target_environmenttype: str = "QualityCheck", additional_arguments_file: str = None, commandlinearguments: list[str] = []) -> None:
2694
- self.__sc.assert_is_git_repository(repository_folder)
2695
- if target_environmenttype == "Development":
2696
- raise ValueError(f"build_codeunitsC is not available for target_environmenttype {target_environmenttype}.")
2697
- # TODO handle additional_arguments_file
2698
- # TODO add option to allow building different codeunits in same project with different images due to their demands
2699
- # TODO check if image provides all demands of codeunit
2700
- self.__sc.run_program("docker", f"run --volume {repository_folder}:/Workspace/Repository " + f"-e repositoryfolder=/Workspace/Repository -e verbosity={verbosity} -e targetenvironment={target_environmenttype} {image}", repository_folder)
2701
-
2702
- @GeneralUtilities.check_arguments
2703
- def build_codeunits(self, repository_folder: str, verbosity: int = 1, target_environmenttype: str = "QualityCheck", additional_arguments_file: str = None, is_pre_merge: bool = False, export_target_directory: str = None, commandline_arguments: list[str] = [], do_git_clean_when_no_changes: bool = False, note: str = None) -> None:
2704
- self.__check_target_environmenttype(target_environmenttype)
2705
- self.__sc.assert_is_git_repository(repository_folder)
2706
- repository_folder = GeneralUtilities.resolve_relative_path_from_current_working_directory(repository_folder)
2707
- codeunits = self.get_codeunits(repository_folder, False)
2708
- project_version = self.get_version_of_project(repository_folder)
2709
-
2710
- now = GeneralUtilities.get_now()
2711
-
2712
- project_resources_folder = os.path.join(repository_folder, "Other", "Scripts")
2713
- PrepareBuildCodeunits_script_name = "PrepareBuildCodeunits.py"
2714
- prepare_build_codeunits_scripts = os.path.join(project_resources_folder, PrepareBuildCodeunits_script_name)
2715
-
2716
- if do_git_clean_when_no_changes and not self.__sc.git_repository_has_uncommitted_changes(repository_folder):
2717
- self.__sc.run_program("git", "clean -dfx", repository_folder)
2718
- if os.path.isfile(prepare_build_codeunits_scripts):
2719
- GeneralUtilities.write_message_to_stdout(f'Run "{PrepareBuildCodeunits_script_name}"')
2720
- result = self.__sc.run_program("python", f"{PrepareBuildCodeunits_script_name}", project_resources_folder, throw_exception_if_exitcode_is_not_zero=False, print_live_output=True)
2721
- if result[0] != 0:
2722
- raise ValueError(f"PrepareBuildCodeunits.py resulted in exitcode {result[0]}.")
2723
-
2724
- self.__do_repository_checks(repository_folder, project_version)
2725
- if not self.__suport_information_exists(repository_folder, project_version):
2726
- support_time = timedelta(days=365*2+30*3+1) # TODO make this configurable
2727
- until = now + support_time
2728
- until_day = datetime(until.year, until.month, until.day, 0, 0, 0)
2729
- from_day = datetime(now.year, now.month, now.day, 0, 0, 0)
2730
- self.mark_current_version_as_supported(repository_folder, project_version, from_day, until_day)
2731
- self.build_specific_codeunits(repository_folder, codeunits, verbosity, target_environmenttype, additional_arguments_file, is_pre_merge, export_target_directory, False, commandline_arguments, do_git_clean_when_no_changes, note)
2732
- self.__save_lines_of_code(repository_folder, project_version)
2733
-
2734
- @GeneralUtilities.check_arguments
2735
- def __save_lines_of_code(self, repository_folder: str, project_version: str) -> None:
2736
- loc = self.__sc.get_lines_of_code_with_default_excluded_patterns(repository_folder)
2737
- loc_metric_folder = os.path.join(repository_folder, "Other", "Metrics")
2738
- GeneralUtilities.ensure_directory_exists(loc_metric_folder)
2739
- loc_metric_file = os.path.join(loc_metric_folder, "LinesOfCode.csv")
2740
- GeneralUtilities.ensure_file_exists(loc_metric_file)
2741
- old_lines = GeneralUtilities.read_lines_from_file(loc_metric_file)
2742
- new_lines = []
2743
- for line in old_lines:
2744
- if not line.startswith(f"v{project_version};"):
2745
- new_lines.append(line)
2746
- new_lines.append(f"v{project_version};{loc}")
2747
- GeneralUtilities.write_lines_to_file(loc_metric_file, new_lines)
2748
-
2749
- @GeneralUtilities.check_arguments
2750
- def build_specific_codeunits(self, repository_folder: str, codeunits: list[str], verbosity: int = 1, target_environmenttype: str = "QualityCheck", additional_arguments_file: str = None, is_pre_merge: bool = False, export_target_directory: str = None, assume_dependent_codeunits_are_already_built: bool = True, commandline_arguments: list[str] = [], do_git_clean_when_no_changes: bool = False, note: str = None, check_for_new_files: bool = True) -> None:
2751
- now_begin: datetime = GeneralUtilities.get_now()
2752
- codeunits_list = "{"+", ".join(codeunits)+"}"
2753
- if verbosity > 2:
2754
- GeneralUtilities.write_message_to_stdout(f"Start building codeunits {codeunits_list} in repository '{repository_folder}'...")
2755
- self.__sc.assert_is_git_repository(repository_folder)
2756
- self.__check_target_environmenttype(target_environmenttype)
2757
- repository_folder = GeneralUtilities.resolve_relative_path_from_current_working_directory(repository_folder)
2758
- repository_name = os.path.basename(repository_folder)
2759
- contains_uncommitted_changes_at_begin = self.__sc.git_repository_has_uncommitted_changes(repository_folder)
2760
- if contains_uncommitted_changes_at_begin:
2761
- if is_pre_merge:
2762
- raise ValueError(f'Repository "{repository_folder}" has uncommitted changes.')
2763
- codeunit_subfolders = [os.path.join(repository_folder, codeunit) for codeunit in codeunits]
2764
- codeunits_with_dependent_codeunits: dict[str, set[str]] = dict[str, set[str]]()
2765
-
2766
- for subfolder in codeunit_subfolders:
2767
- codeunit_name: str = os.path.basename(subfolder)
2768
- codeunit_file = os.path.join(subfolder, f"{codeunit_name}.codeunit.xml")
2769
- GeneralUtilities.assert_condition(os.path.exists(codeunit_file), f"Codeunit-file '{codeunit_file}' does nost exist.")
2770
- codeunits_with_dependent_codeunits[codeunit_name] = self.get_dependent_code_units(codeunit_file)
2771
- sorted_codeunits = self.get_codeunits(repository_folder)
2772
- sorted_codeunits = [codeunit for codeunit in sorted_codeunits if codeunit in codeunits]
2773
- project_version = self.get_version_of_project(repository_folder)
2774
-
2775
- message = f"Build codeunits in product {repository_name}... (Started: {GeneralUtilities.datetime_to_string_for_logfile_entry(now_begin)})"
2776
- if note is not None:
2777
- message = f"{message} ({note})"
2778
- GeneralUtilities.write_message_to_stdout(message)
2779
-
2780
- if len(sorted_codeunits) == 0:
2781
- raise ValueError(f'No codeunit found in subfolders of "{repository_folder}".')
2782
- else:
2783
- if verbosity > 1:
2784
- GeneralUtilities.write_message_to_stdout(f"Attempt to build codeunits ({codeunits_list}) for project version {project_version} in the following order:")
2785
- i = 0
2786
- for codeunit in sorted_codeunits:
2787
- i = i+1
2788
- GeneralUtilities.write_message_to_stdout(f"{i}.: {codeunit}")
2789
- for codeunit in sorted_codeunits:
2790
- GeneralUtilities.write_message_to_stdout(GeneralUtilities.get_line())
2791
- self.__build_codeunit(os.path.join(repository_folder, codeunit), verbosity, target_environmenttype, additional_arguments_file, is_pre_merge, assume_dependent_codeunits_are_already_built, commandline_arguments)
2792
- GeneralUtilities.write_message_to_stdout(GeneralUtilities.get_line())
2793
- contains_uncommitted_changes_at_end = self.__sc.git_repository_has_uncommitted_changes(repository_folder)
2794
- if contains_uncommitted_changes_at_end and (not is_pre_merge) and check_for_new_files:
2795
- if contains_uncommitted_changes_at_begin:
2796
- GeneralUtilities.write_message_to_stdout(f'There are still uncommitted changes in the repository "{repository_folder}".')
2797
- else:
2798
- message = f'Due to the build-process the repository "{repository_folder}" has new uncommitted changes.'
2799
- if target_environmenttype == "Development":
2800
- GeneralUtilities.write_message_to_stderr(f"Warning: {message}")
2801
- else:
2802
- raise ValueError(message)
2803
-
2804
- if export_target_directory is not None:
2805
- project_name = self.get_project_name(repository_folder)
2806
- for codeunit in sorted_codeunits:
2807
- codeunit_version = self.get_version_of_codeunit_folder(os.path.join(repository_folder, codeunit))
2808
- artifacts_folder = os.path.join(repository_folder, codeunit, "Other", "Artifacts")
2809
- target_folder = os.path.join(export_target_directory, project_name, project_version, codeunit)
2810
- GeneralUtilities.ensure_directory_does_not_exist(target_folder)
2811
- GeneralUtilities.ensure_directory_exists(target_folder)
2812
- filename_without_extension = f"{codeunit}.v{codeunit_version}.{target_environmenttype}.Artifacts"
2813
- shutil.make_archive(filename_without_extension, 'zip', artifacts_folder)
2814
- archive_file = os.path.join(os.getcwd(), f"{filename_without_extension}.zip")
2815
- shutil.move(archive_file, target_folder)
2816
-
2817
- now_end: datetime = GeneralUtilities.get_now()
2818
- message2 = f"Finished build codeunits in product {repository_name}. (Finished: {GeneralUtilities.datetime_to_string_for_logfile_entry(now_end)})"
2819
- if note is not None:
2820
- message2 = f"{message2} ({note})"
2821
- GeneralUtilities.write_message_to_stdout(message2)
2822
-
2823
- @GeneralUtilities.check_arguments
2824
- def __do_repository_checks(self, repository_folder: str, project_version: str) -> None: # TDOO move this to a general project-specific (and codeunit-independent-script)
2825
- self.__sc.assert_is_git_repository(repository_folder)
2826
- self.__check_if_changelog_exists(repository_folder, project_version)
2827
- self.__check_whether_security_txt_exists(repository_folder)
2828
- self.__check_whether_general_reference_exists(repository_folder)
2829
- self.__check_whether_workspace_file_exists(repository_folder)
2830
- self.__check_for_staged_or_committed_ignored_files(repository_folder)
2831
-
2832
- @GeneralUtilities.check_arguments
2833
- def __check_whether_general_reference_exists(self, repository_folder: str) -> None:
2834
- GeneralUtilities.assert_file_exists(os.path.join(repository_folder, "Other", "Reference", "Reference.md"))
2835
-
2836
- @GeneralUtilities.check_arguments
2837
- def __check_if_changelog_exists(self, repository_folder: str, project_version: str) -> None:
2838
- self.__sc.assert_is_git_repository(repository_folder)
2839
- changelog_folder = os.path.join(repository_folder, "Other", "Resources", "Changelog")
2840
- changelog_file = os.path.join(changelog_folder, f"v{project_version}.md")
2841
- if not os.path.isfile(changelog_file):
2842
- raise ValueError(f"Changelog-file '{changelog_file}' does not exist. Try creating it using 'sccreatechangelogentry' for example.")
2843
-
2844
- @GeneralUtilities.check_arguments
2845
- def __check_whether_security_txt_exists(self, repository_folder: str) -> None:
2846
- security_txt_file_relative = ".well-known/security.txt"
2847
- security_txt_file = GeneralUtilities.resolve_relative_path(security_txt_file_relative, repository_folder)
2848
- if not os.path.isfile(security_txt_file):
2849
- raise ValueError(f"The repository does not contain a '{security_txt_file_relative}'-file. See https://securitytxt.org/ for more information.")
2850
- # TODO throw error if the date set in the file is expired
2851
- # TODO write wartning if the date set in the file expires soon
2852
-
2853
- @GeneralUtilities.check_arguments
2854
- def __check_for_staged_or_committed_ignored_files(self, repository_folder: str) -> None:
2855
- for file in self.__sc.get_staged_or_committed_git_ignored_files(repository_folder):
2856
- GeneralUtilities.write_message_to_stderr(f'Warning: Repository contains staged or committed file "{file}" which is git-ignored.')
2857
-
2858
- @GeneralUtilities.check_arguments
2859
- def __check_whether_workspace_file_exists(self, repository_folder: str) -> None:
2860
- count = 0
2861
- for file in GeneralUtilities.get_direct_files_of_folder(repository_folder):
2862
- if file.endswith(".code-workspace"):
2863
- count = count + 1
2864
- if count != 1:
2865
- raise ValueError('The repository must contain exactly one ".code-workspace"-file on the top-level.')
2866
-
2867
- @GeneralUtilities.check_arguments
2868
- def update_dependency_in_resources_folder(self, update_dependencies_file, dependency_name: str, latest_version_function: str) -> None:
2869
- dependency_folder = GeneralUtilities.resolve_relative_path(f"../Resources/Dependencies/{dependency_name}", update_dependencies_file)
2870
- version_file = os.path.join(dependency_folder, "Version.txt")
2871
- version_file_exists = os.path.isfile(version_file)
2872
- write_to_file = False
2873
- if version_file_exists:
2874
- current_version = GeneralUtilities.read_text_from_file(version_file)
2875
- if current_version != latest_version_function:
2876
- write_to_file = True
2877
- else:
2878
- GeneralUtilities.ensure_directory_exists(dependency_folder)
2879
- GeneralUtilities.ensure_file_exists(version_file)
2880
- write_to_file = True
2881
- if write_to_file:
2882
- GeneralUtilities.write_text_to_file(version_file, latest_version_function)
2883
-
2884
- @GeneralUtilities.check_arguments
2885
- def __ensure_grylibrary_is_available(self, codeunit_folder: str) -> None:
2886
- self.assert_is_codeunit_folder(codeunit_folder)
2887
- grylibrary_folder = os.path.join(codeunit_folder, "Other", "Resources", "GRYLibrary")
2888
- grylibrary_dll_file = os.path.join(grylibrary_folder, "BuildResult_DotNet_win-x64", "GRYLibrary.dll")
2889
- internet_connection_is_available = GeneralUtilities.internet_connection_is_available()
2890
- grylibrary_dll_file_exists = os.path.isfile(grylibrary_dll_file)
2891
- if internet_connection_is_available: # Load/Update GRYLibrary
2892
- grylibrary_latest_codeunit_file = "https://raw.githubusercontent.com/anionDev/GRYLibrary/stable/GRYLibrary/GRYLibrary.codeunit.xml"
2893
- with urllib.request.urlopen(grylibrary_latest_codeunit_file) as url_result:
2894
- grylibrary_latest_version = self.get_version_of_codeunit_file_content(url_result.read().decode("utf-8"))
2895
- if grylibrary_dll_file_exists:
2896
- grylibrary_existing_codeunit_file = os.path.join(grylibrary_folder, "SourceCode", "GRYLibrary.codeunit.xml")
2897
- grylibrary_existing_codeunit_version = self.get_version_of_codeunit(grylibrary_existing_codeunit_file)
2898
- if grylibrary_existing_codeunit_version != grylibrary_latest_version:
2899
- GeneralUtilities.ensure_directory_does_not_exist(grylibrary_folder)
2900
- if not os.path.isfile(grylibrary_dll_file):
2901
- GeneralUtilities.ensure_directory_does_not_exist(grylibrary_folder)
2902
- GeneralUtilities.ensure_directory_exists(grylibrary_folder)
2903
- archive_name = f"GRYLibrary.v{grylibrary_latest_version}.Productive.Artifacts.zip"
2904
- archive_download_link = f"https://github.com/anionDev/GRYLibrary/releases/download/v{grylibrary_latest_version}/{archive_name}"
2905
- archive_file = os.path.join(grylibrary_folder, archive_name)
2906
- urllib.request.urlretrieve(archive_download_link, archive_file)
2907
- with zipfile.ZipFile(archive_file, 'r') as zip_ref:
2908
- zip_ref.extractall(grylibrary_folder)
2909
- GeneralUtilities.ensure_file_does_not_exist(archive_file)
2910
- else:
2911
- if grylibrary_dll_file_exists:
2912
- GeneralUtilities.write_message_to_stdout("Warning: Can not check for updates of GRYLibrary due to missing internet-connection.")
2913
- else:
2914
- raise ValueError("Can not download GRYLibrary.")
2915
-
2916
- @GeneralUtilities.check_arguments
2917
- def ensure_ffmpeg_is_available(self, codeunit_folder: str) -> None:
2918
- self.assert_is_codeunit_folder(codeunit_folder)
2919
- ffmpeg_folder = os.path.join(codeunit_folder, "Other", "Resources", "FFMPEG")
2920
- internet_connection_is_available = GeneralUtilities.internet_connection_is_available()
2921
- exe_file = f"{ffmpeg_folder}/ffmpeg.exe"
2922
- exe_file_exists = os.path.isfile(exe_file)
2923
- if internet_connection_is_available: # Load/Update
2924
- GeneralUtilities.ensure_directory_does_not_exist(ffmpeg_folder)
2925
- GeneralUtilities.ensure_directory_exists(ffmpeg_folder)
2926
- ffmpeg_temp_folder = ffmpeg_folder+"Temp"
2927
- GeneralUtilities.ensure_directory_does_not_exist(ffmpeg_temp_folder)
2928
- GeneralUtilities.ensure_directory_exists(ffmpeg_temp_folder)
2929
- zip_file_on_disk = os.path.join(ffmpeg_temp_folder, "ffmpeg.zip")
2930
- original_zip_filename = "ffmpeg-master-latest-win64-gpl-shared"
2931
- zip_link = f"https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/{original_zip_filename}.zip"
2932
- urllib.request.urlretrieve(zip_link, zip_file_on_disk)
2933
- shutil.unpack_archive(zip_file_on_disk, ffmpeg_temp_folder)
2934
- bin_folder_source = os.path.join(ffmpeg_temp_folder, "ffmpeg-master-latest-win64-gpl-shared/bin")
2935
- bin_folder_target = ffmpeg_folder
2936
- GeneralUtilities.copy_content_of_folder(bin_folder_source, bin_folder_target)
2937
- GeneralUtilities.ensure_directory_does_not_exist(ffmpeg_temp_folder)
2938
- else:
2939
- if exe_file_exists:
2940
- GeneralUtilities.write_message_to_stdout("Warning: Can not check for updates of FFMPEG due to missing internet-connection.")
2941
- else:
2942
- raise ValueError("Can not download FFMPEG.")
2943
-
2944
- @GeneralUtilities.check_arguments
2945
- def ensure_plantuml_is_available(self, target_folder: str) -> None:
2946
- self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "plantuml", "plantuml", "PlantUML", "plantuml.jar", lambda latest_version: "plantuml.jar")
2947
-
2948
- @GeneralUtilities.check_arguments
2949
- def ensure_androidappbundletool_is_available(self, target_folder: str) -> None:
2950
- self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "google", "bundletool", "AndroidAppBundleTool", "bundletool.jar", lambda latest_version: f"bundletool-all-{latest_version}.jar")
2951
-
2952
- @GeneralUtilities.check_arguments
2953
- def ensure_mediamtx_is_available(self, target_folder: str) -> None:
2954
- def download_and_extract(osname: str, osname_in_github_asset: str, extension: str):
2955
- resource_name: str = f"MediaMTX_{osname}"
2956
- zip_filename: str = f"{resource_name}.{extension}"
2957
- self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "bluenviron", "mediamtx", resource_name, zip_filename, lambda latest_version: f"mediamtx_{latest_version}_{osname_in_github_asset}_amd64.{extension}")
2958
- resource_folder: str = os.path.join(target_folder, "Other", "Resources", resource_name)
2959
- target_folder_extracted = os.path.join(resource_folder, "MediaMTX")
2960
- local_zip_file: str = os.path.join(resource_folder, f"{resource_name}.{extension}")
2961
- GeneralUtilities.ensure_folder_exists_and_is_empty(target_folder_extracted)
2962
- if extension == "zip":
2963
- with zipfile.ZipFile(local_zip_file, 'r') as zip_ref:
2964
- zip_ref.extractall(target_folder_extracted)
2965
- elif extension == "tar.gz":
2966
- with tarfile.open(local_zip_file, "r:gz") as tar:
2967
- tar.extractall(path=target_folder_extracted)
2968
- else:
2969
- raise ValueError(f"Unknown extension: \"{extension}\"")
2970
- GeneralUtilities.ensure_file_does_not_exist(local_zip_file)
2971
-
2972
- download_and_extract("Windows", "windows", "zip")
2973
- download_and_extract("Linux", "linux", "tar.gz")
2974
- download_and_extract("MacOS", "darwin", "tar.gz")
2975
-
2976
- @GeneralUtilities.check_arguments
2977
- def ensure_cyclonedxcli_is_available(self, target_folder: str) -> None:
2978
- local_filename = "cyclonedx-cli"
2979
- filename_on_github: str
2980
- if GeneralUtilities.current_system_is_windows():
2981
- filename_on_github = "cyclonedx-win-x64.exe"
2982
- local_filename = local_filename+".exe"
2983
- else:
2984
- filename_on_github = "cyclonedx-linux-x64"
2985
- self.ensure_file_from_github_assets_is_available_with_retry(target_folder, "CycloneDX", "cyclonedx-cli", "CycloneDXCLI", local_filename, lambda latest_version: filename_on_github)
2986
-
2987
- @GeneralUtilities.check_arguments
2988
- def ensure_file_from_github_assets_is_available_with_retry(self, target_folder: str, githubuser: str, githubprojectname: str, resource_name: str, local_filename: str, get_filename_on_github, amount_of_attempts: int = 5) -> None:
2989
- GeneralUtilities.retry_action(lambda: self.ensure_file_from_github_assets_is_available(target_folder, githubuser, githubprojectname, resource_name, local_filename, get_filename_on_github), amount_of_attempts)
2990
-
2991
- @GeneralUtilities.check_arguments
2992
- def ensure_file_from_github_assets_is_available(self, target_folder: str, githubuser: str, githubprojectname: str, resource_name: str, local_filename: str, get_filename_on_github) -> None:
2993
- resource_folder = os.path.join(target_folder, "Other", "Resources", resource_name)
2994
- internet_connection_is_available = GeneralUtilities.internet_connection_is_available()
2995
- file = f"{resource_folder}/{local_filename}"
2996
- file_exists = os.path.isfile(file)
2997
- if internet_connection_is_available: # Load/Update
2998
- GeneralUtilities.ensure_directory_does_not_exist(resource_folder)
2999
- GeneralUtilities.ensure_directory_exists(resource_folder)
3000
- headers = {'Cache-Control': 'no-cache'}
3001
- response = requests.get(f"https://api.github.com/repos/{githubuser}/{githubprojectname}/releases/latest", timeout=10, headers=headers)
3002
- latest_version = response.json()["tag_name"]
3003
- filename_on_github = get_filename_on_github(latest_version)
3004
- link = f"https://github.com/{githubuser}/{githubprojectname}/releases/download/{latest_version}/{filename_on_github}"
3005
- urllib.request.urlretrieve(link, file)
3006
- else:
3007
- if file_exists:
3008
- GeneralUtilities.write_message_to_stdout(f"Warning: Can not check for updates of {resource_name} due to missing internet-connection.")
3009
- else:
3010
- raise ValueError(f"Can not download {resource_name}.")
3011
-
3012
- @GeneralUtilities.check_arguments
3013
- def generate_svg_files_from_plantuml_files_for_repository(self, repository_folder: str) -> None:
3014
- self.__sc.assert_is_git_repository(repository_folder)
3015
- self.ensure_plantuml_is_available(repository_folder)
3016
- plant_uml_folder = os.path.join(repository_folder, "Other", "Resources", "PlantUML")
3017
- target_folder = os.path.join(repository_folder, "Other", "Reference")
3018
- self.__generate_svg_files_from_plantuml(target_folder, plant_uml_folder)
3019
-
3020
- @GeneralUtilities.check_arguments
3021
- def generate_svg_files_from_plantuml_files_for_codeunit(self, codeunit_folder: str) -> None:
3022
- self.assert_is_codeunit_folder(codeunit_folder)
3023
- repository_folder = os.path.dirname(codeunit_folder)
3024
- self.ensure_plantuml_is_available(repository_folder)
3025
- plant_uml_folder = os.path.join(repository_folder, "Other", "Resources", "PlantUML")
3026
- target_folder = os.path.join(codeunit_folder, "Other", "Reference")
3027
- self.__generate_svg_files_from_plantuml(target_folder, plant_uml_folder)
3028
-
3029
- @GeneralUtilities.check_arguments
3030
- def __generate_svg_files_from_plantuml(self, diagrams_files_folder: str, plant_uml_folder: str) -> None:
3031
- for file in GeneralUtilities.get_all_files_of_folder(diagrams_files_folder):
3032
- if file.endswith(".plantuml"):
3033
- output_filename = self.get_output_filename_for_plantuml_filename(file)
3034
- argument = ['-jar', f'{plant_uml_folder}/plantuml.jar', '-tsvg', os.path.basename(file)]
3035
- folder = os.path.dirname(file)
3036
- self.__sc.run_program_argsasarray("java", argument, folder, verbosity=0)
3037
- result_file = folder+"/" + output_filename
3038
- GeneralUtilities.assert_file_exists(result_file)
3039
- self.__sc.format_xml_file(result_file)
3040
-
3041
- @GeneralUtilities.check_arguments
3042
- def get_output_filename_for_plantuml_filename(self, plantuml_file: str) -> str:
3043
- for line in GeneralUtilities.read_lines_from_file(plantuml_file):
3044
- prefix = "@startuml "
3045
- if line.startswith(prefix):
3046
- title = line[len(prefix):]
3047
- return title+".svg"
3048
- return Path(plantuml_file).stem+".svg"
3049
-
3050
- @GeneralUtilities.check_arguments
3051
- def generate_codeunits_overview_diagram(self, repository_folder: str) -> None:
3052
- self.__sc.assert_is_git_repository(repository_folder)
3053
- project_name: str = os.path.basename(repository_folder)
3054
- target_folder = os.path.join(repository_folder, "Other", "Reference", "Technical", "Diagrams")
3055
- GeneralUtilities.ensure_directory_exists(target_folder)
3056
- target_file = os.path.join(target_folder, "CodeUnits-Overview.plantuml")
3057
- lines = ["@startuml CodeUnits-Overview"]
3058
- lines.append(f"title CodeUnits of {project_name}")
3059
-
3060
- codeunits = self.get_codeunits(repository_folder)
3061
- for codeunitname in codeunits:
3062
- codeunit_file: str = os.path.join(repository_folder, codeunitname, f"{codeunitname}.codeunit.xml")
3063
-
3064
- description = self.get_codeunit_description(codeunit_file)
3065
-
3066
- lines.append(GeneralUtilities.empty_string)
3067
- lines.append(f"[{codeunitname}]")
3068
- lines.append(f"note as {codeunitname}Note")
3069
- lines.append(f" {description}")
3070
- lines.append(f"end note")
3071
- lines.append(f"{codeunitname} .. {codeunitname}Note")
3072
-
3073
- lines.append(GeneralUtilities.empty_string)
3074
- for codeunitname in codeunits:
3075
- codeunit_file: str = os.path.join(repository_folder, codeunitname, f"{codeunitname}.codeunit.xml")
3076
- dependent_codeunits = self.get_dependent_code_units(codeunit_file)
3077
- for dependent_codeunit in dependent_codeunits:
3078
- lines.append(f"{codeunitname} --> {dependent_codeunit}")
3079
-
3080
- lines.append(GeneralUtilities.empty_string)
3081
- lines.append("@enduml")
3082
-
3083
- GeneralUtilities.write_lines_to_file(target_file, lines)
3084
-
3085
- @GeneralUtilities.check_arguments
3086
- def load_deb_control_file_content(self, file: str, codeunitname: str, codeunitversion: str, installedsize: int, maintainername: str, maintaineremail: str, description: str,) -> str:
3087
- content = GeneralUtilities.read_text_from_file(file)
3088
- content = GeneralUtilities.replace_variable_in_string(content, "codeunitname", codeunitname)
3089
- content = GeneralUtilities.replace_variable_in_string(content, "codeunitversion", codeunitversion)
3090
- content = GeneralUtilities.replace_variable_in_string(content, "installedsize", str(installedsize))
3091
- content = GeneralUtilities.replace_variable_in_string(content, "maintainername", maintainername)
3092
- content = GeneralUtilities.replace_variable_in_string(content, "maintaineremail", maintaineremail)
3093
- content = GeneralUtilities.replace_variable_in_string(content, "description", description)
3094
- return content
3095
-
3096
- @GeneralUtilities.check_arguments
3097
- def calculate_deb_package_size(self, binary_folder: str) -> int:
3098
- size_in_bytes = 0
3099
- for file in GeneralUtilities.get_all_files_of_folder(binary_folder):
3100
- size_in_bytes = size_in_bytes+os.path.getsize(file)
3101
- result = math.ceil(size_in_bytes/1024)
3102
- return result
3103
-
3104
- @GeneralUtilities.check_arguments
3105
- def create_deb_package_for_artifact(self, codeunit_folder: str, maintainername: str, maintaineremail: str, description: str, verbosity: int, cmd_arguments: list[str]) -> None:
3106
- self.assert_is_codeunit_folder(codeunit_folder)
3107
- verbosity = self.get_verbosity_from_commandline_arguments(cmd_arguments, verbosity)
3108
- codeunit_name = os.path.basename(codeunit_folder)
3109
- binary_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts/BuildResult_DotNet_linux-x64", codeunit_folder)
3110
- deb_output_folder = GeneralUtilities.resolve_relative_path("Other/Artifacts/BuildResult_Deb", codeunit_folder)
3111
- control_file = GeneralUtilities.resolve_relative_path("Other/Build/DebControlFile.txt", codeunit_folder)
3112
- installedsize = self.calculate_deb_package_size(binary_folder)
3113
- control_file_content = self.load_deb_control_file_content(control_file, codeunit_name, self.get_version_of_codeunit_folder(codeunit_folder), installedsize, maintainername, maintaineremail, description)
3114
- self.__sc.create_deb_package(codeunit_name, binary_folder, control_file_content, deb_output_folder, verbosity, 555)
3115
-
3116
- @GeneralUtilities.check_arguments
3117
- def create_zip_file_for_artifact(self, codeunit_folder: str, artifact_source_name: str, name_of_new_artifact: str, verbosity: int, cmd_arguments: list[str]) -> None:
3118
- self.assert_is_codeunit_folder(codeunit_folder)
3119
- verbosity = self.get_verbosity_from_commandline_arguments(cmd_arguments, verbosity)
3120
- src_artifact_folder = GeneralUtilities.resolve_relative_path(f"Other/Artifacts/{artifact_source_name}", codeunit_folder)
3121
- shutil.make_archive(name_of_new_artifact, 'zip', src_artifact_folder)
3122
- archive_file = os.path.join(os.getcwd(), f"{name_of_new_artifact}.zip")
3123
- target_folder = GeneralUtilities.resolve_relative_path(f"Other/Artifacts/{name_of_new_artifact}", codeunit_folder)
3124
- GeneralUtilities.ensure_folder_exists_and_is_empty(target_folder)
3125
- shutil.move(archive_file, target_folder)
3126
-
3127
- def generate_winget_zip_manifest(self, codeunit_folder: str, artifact_name_of_zip: str):
3128
- self.assert_is_codeunit_folder(codeunit_folder)
3129
- codeunit_version = self.get_version_of_codeunit_folder(codeunit_folder)
3130
- build_folder = os.path.join(codeunit_folder, "Other", "Build")
3131
- artifacts_folder = os.path.join(codeunit_folder, "Other", "Artifacts", artifact_name_of_zip)
3132
- manifest_folder = os.path.join(codeunit_folder, "Other", "Artifacts", "WinGet-Manifest")
3133
- GeneralUtilities.assert_folder_exists(artifacts_folder)
3134
- artifacts_file = self.__sc.find_file_by_extension(artifacts_folder, "zip")
3135
- winget_template_file = os.path.join(build_folder, "WinGet-Template.yaml")
3136
- winget_manifest_file = os.path.join(manifest_folder, "WinGet-Manifest.yaml")
3137
- GeneralUtilities.assert_file_exists(winget_template_file)
3138
- GeneralUtilities.ensure_directory_exists(manifest_folder)
3139
- GeneralUtilities.ensure_file_exists(winget_manifest_file)
3140
- manifest_content = GeneralUtilities.read_text_from_file(winget_template_file)
3141
- manifest_content = GeneralUtilities.replace_variable_in_string(manifest_content, "version", codeunit_version)
3142
- manifest_content = GeneralUtilities.replace_variable_in_string(manifest_content, "sha256_hashvalue", GeneralUtilities.get_sha256_of_file(artifacts_file))
3143
- GeneralUtilities.write_text_to_file(winget_manifest_file, manifest_content)
3144
-
3145
- @GeneralUtilities.check_arguments
3146
- def update_year_in_license_file_in_common_scripts_file(self, common_tasks_scripts_file: str) -> None:
3147
- self.update_year_in_license_file(GeneralUtilities.resolve_relative_path("../../..", common_tasks_scripts_file))
3148
-
3149
- @GeneralUtilities.check_arguments
3150
- def update_year_in_license_file(self, repository_folder: str) -> None:
3151
- self.__sc.update_year_in_first_line_of_file(os.path.join(repository_folder, "License.txt"))
3152
-
3153
- @GeneralUtilities.check_arguments
3154
- def update_year_for_dotnet_codeunit_in_common_scripts_file(self, common_tasks_scripts_file: str) -> None:
3155
- self.update_year_for_dotnet_codeunit(GeneralUtilities.resolve_relative_path("../..", common_tasks_scripts_file))
3156
-
3157
- @GeneralUtilities.check_arguments
3158
- def update_year_for_dotnet_codeunit(self, codeunit_folder: str) -> None:
3159
- self.assert_is_codeunit_folder(codeunit_folder)
3160
- codeunit_name = os.path.basename(codeunit_folder)
3161
- csproj_file = os.path.join(codeunit_folder, codeunit_name, f"{codeunit_name}.csproj")
3162
- self.__sc.update_year_in_copyright_tags(csproj_file)
3163
- csprojtests_file = os.path.join(codeunit_folder, f"{codeunit_name}Tests", f"{codeunit_name}Tests.csproj")
3164
- self.__sc.update_year_in_copyright_tags(csprojtests_file)
3165
- nuspec_file = os.path.join(codeunit_folder, "Other", "Build", f"{codeunit_name}.nuspec")
3166
- if os.path.isfile(nuspec_file):
3167
- self.__sc.update_year_in_copyright_tags(nuspec_file)
3168
-
3169
- @GeneralUtilities.check_arguments
3170
- def repository_has_codeunits(self, repository: str, ignore_disabled_codeunits: bool = True) -> bool:
3171
- return len(self.get_codeunits(repository, ignore_disabled_codeunits))
3172
-
3173
- @GeneralUtilities.check_arguments
3174
- def verify_artifact_exists(self, codeunit_folder: str, artifact_name_regexes: dict[str, bool]) -> None:
3175
- self.assert_is_codeunit_folder(codeunit_folder)
3176
- codeunit_name: str = os.path.basename(codeunit_folder)
3177
- artifacts_folder = os.path.join(codeunit_folder, "Other/Artifacts")
3178
- existing_artifacts = [os.path.basename(x) for x in GeneralUtilities.get_direct_folders_of_folder(artifacts_folder)]
3179
- for artifact_name_regex, required in artifact_name_regexes.items():
3180
- artifact_exists = False
3181
- for existing_artifact in existing_artifacts:
3182
- pattern = re.compile(artifact_name_regex)
3183
- if pattern.match(existing_artifact):
3184
- artifact_exists = True
3185
- if not artifact_exists:
3186
- message = f"Codeunit {codeunit_name} does not contain an artifact which matches the name '{artifact_name_regex}'."
3187
- if required:
3188
- raise ValueError(message)
3189
- else:
3190
- GeneralUtilities.write_message_to_stderr(f"Warning: {message}")
3191
-
3192
- @GeneralUtilities.check_arguments
3193
- def __build_codeunit(self, codeunit_folder: str, verbosity: int = 1, target_environmenttype: str = "QualityCheck", additional_arguments_file: str = None, is_pre_merge: bool = False, assume_dependent_codeunits_are_already_built: bool = False, commandline_arguments: list[str] = []) -> None:
3194
- self.assert_is_codeunit_folder(codeunit_folder)
3195
- now = GeneralUtilities.get_now()
3196
- codeunit_folder = GeneralUtilities.resolve_relative_path_from_current_working_directory(codeunit_folder)
3197
- repository_folder = GeneralUtilities.resolve_relative_path("..", codeunit_folder)
3198
- codeunit_name: str = os.path.basename(codeunit_folder)
3199
- if verbosity > 2:
3200
- GeneralUtilities.write_message_to_stdout(f"Start building codeunit {codeunit_name}")
3201
- codeunit_file = os.path.join(codeunit_folder, f"{codeunit_name}.codeunit.xml")
3202
-
3203
- if (not os.path.isfile(codeunit_file)):
3204
- raise ValueError(f'"{codeunit_folder}" is no codeunit-folder.')
3205
-
3206
- if not self.codeunit_is_enabled(codeunit_file):
3207
- GeneralUtilities.write_message_to_stdout(f"Warning: Codeunit {codeunit_name} is disabled.")
3208
- return
3209
-
3210
- GeneralUtilities.write_message_to_stdout(f"Start building codeunit {codeunit_name}.")
3211
- GeneralUtilities.write_message_to_stdout(f"Build-environmenttype: {target_environmenttype}")
3212
- if not self.__sc.git_repository_has_uncommitted_changes(repository_folder):
3213
- self.__sc.run_program("git", "clean -dfx", codeunit_folder)
3214
-
3215
- verbosity_for_executed_programs = self.get_verbosity_from_commandline_arguments(commandline_arguments, verbosity)
3216
-
3217
- other_folder = os.path.join(codeunit_folder, "Other")
3218
- build_folder = os.path.join(other_folder, "Build")
3219
- quality_folder = os.path.join(other_folder, "QualityCheck")
3220
- reference_folder = os.path.join(other_folder, "Reference")
3221
- additional_arguments_c: str = GeneralUtilities.empty_string
3222
- additional_arguments_b: str = GeneralUtilities.empty_string
3223
- additional_arguments_r: str = GeneralUtilities.empty_string
3224
- additional_arguments_l: str = GeneralUtilities.empty_string
3225
- additional_arguments_g: str = GeneralUtilities.empty_string
3226
- additional_arguments_f: str = GeneralUtilities.empty_string
3227
- general_argument = f' --overwrite_verbosity {str(verbosity)} --overwrite_targetenvironmenttype {target_environmenttype}'
3228
-
3229
- c_additionalargumentsfile_argument = GeneralUtilities.empty_string
3230
-
3231
- if is_pre_merge:
3232
- general_argument = general_argument+" --overwrite_is_pre_merge true"
3233
- GeneralUtilities.write_message_to_stdout("This is a pre-merge-build")
3234
-
3235
- if assume_dependent_codeunits_are_already_built:
3236
- c_additionalargumentsfile_argument = c_additionalargumentsfile_argument+" --overwrite_assume_dependent_codeunits_are_already_built true"
3237
- diagnostic = False
3238
- if diagnostic:
3239
- GeneralUtilities.write_message_to_stdout("Assume dependent codeunits are already built")
3240
-
3241
- if additional_arguments_file is not None:
3242
- config = configparser.ConfigParser()
3243
- config.read(additional_arguments_file)
3244
- section_name = f"{codeunit_name}_Configuration"
3245
- if config.has_option(section_name, "ArgumentsForCommonTasks"):
3246
- additional_arguments_c = " " + config.get(section_name, "ArgumentsForCommonTasks")
3247
- if config.has_option(section_name, "ArgumentsForBuild"):
3248
- additional_arguments_b = " " + config.get(section_name, "ArgumentsForBuild")
3249
- if config.has_option(section_name, "ArgumentsForRunTestcases"):
3250
- additional_arguments_r = " " + config.get(section_name, "ArgumentsForRunTestcases")
3251
- if config.has_option(section_name, "ArgumentsForLinting"):
3252
- additional_arguments_l = " " + config.get(section_name, "ArgumentsForLinting")
3253
- if config.has_option(section_name, "ArgumentsForGenerateReference"):
3254
- additional_arguments_g = " " + config.get(section_name, "ArgumentsForGenerateReference")
3255
- if config.has_option(section_name, "ArgumentsForOnFinish"):
3256
- additional_arguments_f = " " + config.get(section_name, "ArgumentsForOnFinish")
3257
- c_additionalargumentsfile_argument = f' --overwrite_additionalargumentsfile "{additional_arguments_file}"'
3258
-
3259
- GeneralUtilities.write_message_to_stdout('Run "CommonTasks.py"...')
3260
- self.__sc.run_program("python", f"CommonTasks.py{additional_arguments_c}{general_argument}{c_additionalargumentsfile_argument}", other_folder, verbosity=verbosity_for_executed_programs, throw_exception_if_exitcode_is_not_zero=True, print_live_output=2 < verbosity)
3261
- self.verify_artifact_exists(codeunit_folder, dict[str, bool]({"Changelog": False, "License": True, "DiffReport": True}))
3262
-
3263
- GeneralUtilities.write_message_to_stdout('Run "Build.py"...')
3264
- self.__sc.run_program("python", f"Build.py{additional_arguments_b}{general_argument}", build_folder, verbosity=verbosity_for_executed_programs, throw_exception_if_exitcode_is_not_zero=True, print_live_output=2 < verbosity)
3265
-
3266
- artifacts = {"BuildResult_.+": True, "BOM": False, "SourceCode": True}
3267
- if self.codeunit_has_testable_sourcecode(codeunit_file):
3268
- artifacts["CodeAnalysisResult"] = False
3269
- self.verify_artifact_exists(codeunit_folder, dict[str, bool](artifacts))
3270
-
3271
- codeunit_hast_testable_sourcecode = self.codeunit_has_testable_sourcecode(codeunit_file)
3272
- if codeunit_hast_testable_sourcecode:
3273
- GeneralUtilities.write_message_to_stdout('Run "RunTestcases.py"...')
3274
- self.__sc.run_program("python", f"RunTestcases.py{additional_arguments_r}{general_argument}", quality_folder, verbosity=verbosity_for_executed_programs, throw_exception_if_exitcode_is_not_zero=True, print_live_output=2 < verbosity)
3275
- self.verify_artifact_exists(codeunit_folder, dict[str, bool]({"TestCoverage": True, "TestCoverageReport": False}))
3276
-
3277
- GeneralUtilities.write_message_to_stdout('Run "Linting.py"...')
3278
- self.__sc.run_program("python", f"Linting.py{additional_arguments_l}{general_argument}", quality_folder, verbosity=verbosity_for_executed_programs, throw_exception_if_exitcode_is_not_zero=True, print_live_output=2 < verbosity)
3279
- self.verify_artifact_exists(codeunit_folder, dict[str, bool]())
3280
-
3281
- GeneralUtilities.write_message_to_stdout('Run "GenerateReference.py"...')
3282
- self.__sc.run_program("python", f"GenerateReference.py{additional_arguments_g}{general_argument}", reference_folder, verbosity=verbosity_for_executed_programs, throw_exception_if_exitcode_is_not_zero=True, print_live_output=2 < verbosity)
3283
- self.verify_artifact_exists(codeunit_folder, dict[str, bool]({"Reference": True}))
3284
-
3285
- if os.path.isfile(os.path.join(other_folder, "OnBuildingFinished.py")):
3286
- GeneralUtilities.write_message_to_stdout('Run "OnBuildingFinished.py"...')
3287
- self.__sc.run_program("python", f"OnBuildingFinished.py{additional_arguments_f}{general_argument}", other_folder, verbosity=verbosity_for_executed_programs, throw_exception_if_exitcode_is_not_zero=True, print_live_output=2 < verbosity)
3288
-
3289
- artifacts_folder = os.path.join(codeunit_folder, "Other", "Artifacts")
3290
- artifactsinformation_file = os.path.join(artifacts_folder, f"{codeunit_name}.artifactsinformation.xml")
3291
- codeunit_version = self.get_version_of_codeunit(codeunit_file)
3292
- GeneralUtilities.ensure_file_exists(artifactsinformation_file)
3293
- artifacts_list = []
3294
- for artifact_folder in GeneralUtilities.get_direct_folders_of_folder(artifacts_folder):
3295
- artifact_name = os.path.basename(artifact_folder)
3296
- artifacts_list.append(f" <cps:artifact>{artifact_name}<cps:artifact>")
3297
- artifacts = '\n'.join(artifacts_list)
3298
- moment = GeneralUtilities.datetime_to_string(now)
3299
- # TODO implement usage of self.reference_latest_version_of_xsd_when_generating_xml
3300
- GeneralUtilities.write_text_to_file(artifactsinformation_file, f"""<?xml version="1.0" encoding="UTF-8" ?>
3301
- <cps:artifactsinformation xmlns:cps="https://projects.aniondev.de/PublicProjects/Common/ProjectTemplates/-/tree/main/Conventions/RepositoryStructure/CommonProjectStructure" artifactsinformationspecificationversion="1.0.0"
3302
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://raw.githubusercontent.com/anionDev/ProjectTemplates/main/Templates/Conventions/RepositoryStructure/CommonProjectStructure/artifactsinformation.xsd">
3303
- <cps:name>{codeunit_name}</cps:name>
3304
- <cps:version>{codeunit_version}</cps:version>
3305
- <cps:timestamp>{moment}</cps:timestamp>
3306
- <cps:targetenvironmenttype>{target_environmenttype}</cps:targetenvironmenttype>
3307
- <cps:artifacts>
3308
- {artifacts}
3309
- </cps:artifacts>
3310
- </cps:artifactsinformation>""")
3311
- # TODO validate artifactsinformation_file against xsd
3312
- GeneralUtilities.write_message_to_stdout(f"Finished building codeunit {codeunit_name} without errors.")
3313
-
3314
- def __ensure_changelog_file_is_added(self, repository_folder: str, version_of_project: str):
3315
- changelog_file = os.path.join(repository_folder, "Other", "Resources", "Changelog", f"v{version_of_project}.md")
3316
- if not os.path.isfile(changelog_file):
3317
- GeneralUtilities.ensure_file_exists(changelog_file)
3318
- GeneralUtilities.write_text_to_file(changelog_file, """# Release notes
3319
-
3320
- ## Changes
3321
-
3322
- - Updated dependencies.
3323
- """)
3324
-
3325
- @GeneralUtilities.check_arguments
3326
- def generic_update_dependencies(self, repository_folder: str, verbosity: int = 1):
3327
- # Prepare
3328
- GeneralUtilities.write_message_to_stdout("Update dependencies...")
3329
- self.__sc.assert_is_git_repository(repository_folder)
3330
- codeunits = self.get_codeunits(repository_folder)
3331
- update_dependencies_script_filename = "UpdateDependencies.py"
3332
- target_environmenttype = "QualityCheck"
3333
- project_name: str = os.path.basename(repository_folder)
3334
- GeneralUtilities.assert_condition(not self.__sc.git_repository_has_uncommitted_changes(repository_folder), "There are uncommitted changes in the repository.")
3335
- self.build_codeunits(repository_folder, target_environmenttype=target_environmenttype, do_git_clean_when_no_changes=True, note="Prepare dependency-update") # Required because update dependencies is not always possible for not-buildet codeunits (depends on the programming language or package manager)
3336
-
3337
- # update dependencies of resources
3338
- global_scripts_folder = os.path.join(repository_folder, "Other", "Scripts")
3339
- if os.path.isfile(os.path.join(global_scripts_folder, update_dependencies_script_filename)):
3340
- self.__sc.run_program("python", update_dependencies_script_filename, global_scripts_folder, print_live_output=True)
3341
- version_of_project = self.get_version_of_project(repository_folder)
3342
- self.__ensure_changelog_file_is_added(repository_folder, version_of_project)
3343
- GeneralUtilities.write_message_to_stdout(f"Updated global dependencies of {project_name}.")
3344
- self.build_codeunits(repository_folder, verbosity, "QualityCheck", None, False, None, [], False, "Build codeunits due to updated product-wide dependencies")
3345
-
3346
- # update dependencies of codeunits
3347
- for codeunit in codeunits:
3348
- codeunit_file = os.path.join(repository_folder, codeunit, f"{codeunit}.codeunit.xml")
3349
- codeunit_has_updatable_dependencies = self.codeunit_has_updatable_dependencies(codeunit_file)
3350
- codeunit_folder: str = os.path.join(repository_folder, codeunit)
3351
- self.build_codeunit(codeunit_folder, verbosity, "QualityCheck", None, False, None, False, [])
3352
- if codeunit_has_updatable_dependencies:
3353
- codeunit_folder = os.path.join(repository_folder, codeunit)
3354
- update_dependencies_script_folder = os.path.join(codeunit_folder, "Other")
3355
- GeneralUtilities.ensure_directory_exists(os.path.join(update_dependencies_script_folder, "Resources", "CodeAnalysisResult"))
3356
- self.__sc.run_program("python", update_dependencies_script_filename, update_dependencies_script_folder, verbosity, print_live_output=True)
3357
- if self.__sc.git_repository_has_uncommitted_changes(repository_folder):
3358
- version_of_project = self.get_version_of_project(repository_folder)
3359
- self.__ensure_changelog_file_is_added(repository_folder, version_of_project)
3360
- GeneralUtilities.write_message_to_stdout(f"Updated dependencies in codeunit {codeunit}.")
3361
-
3362
- self.build_codeunits(repository_folder, verbosity, "QualityCheck", None, False, None, [], False, "Build all codeunits due to updated dependencies")
3363
- self.__sc.git_commit(repository_folder, "Updated dependencies")
3364
-
3365
- class GenericPrepareNewReleaseArguments:
3366
- current_file: str
3367
- product_name: str
3368
- commandline_arguments: list[str]
3369
-
3370
- def __init__(self, current_file: str, product_name: str, commandline_arguments: list[str]):
3371
- self.current_file = current_file
3372
- self.product_name = product_name
3373
- self.commandline_arguments = commandline_arguments
3374
-
3375
- @GeneralUtilities.check_arguments
3376
- def generic_prepare_new_release(self, generic_prepare_new_release_arguments: GenericPrepareNewReleaseArguments):
3377
- GeneralUtilities.write_message_to_stdout(f"Prepare release for {generic_prepare_new_release_arguments.product_name}.")
3378
-
3379
- # constants
3380
- folder_of_this_file = os.path.dirname(generic_prepare_new_release_arguments.current_file)
3381
- build_repository_folder = GeneralUtilities.resolve_relative_path("../..", folder_of_this_file)
3382
- self.__sc.assert_is_git_repository(build_repository_folder)
3383
-
3384
- repository_folder = GeneralUtilities.resolve_relative_path(f"../../Submodules/{generic_prepare_new_release_arguments.product_name}", folder_of_this_file)
3385
- self.__sc.assert_is_git_repository(repository_folder)
3386
- reference_folder = repository_folder+"Reference"
3387
- self.__sc.assert_is_git_repository(reference_folder)
3388
- verbosity: int = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(generic_prepare_new_release_arguments.commandline_arguments, 1)
3389
-
3390
- merge_source_branch = "other/next-release" # maybe this should be configurable
3391
- main_branch = "main" # maybe this should be configurable
3392
-
3393
- # prepare
3394
- self.assert_no_uncommitted_changes(repository_folder)
3395
- self.assert_no_uncommitted_changes(reference_folder)
3396
- self.assert_no_uncommitted_changes(build_repository_folder)
3397
- self.__sc.git_checkout(build_repository_folder, "main", True)
3398
- self.__sc.git_checkout(repository_folder, merge_source_branch, True)
3399
- self.__sc.git_checkout(reference_folder, "main", True)
3400
- self.assert_no_uncommitted_changes(repository_folder)
3401
- self.assert_no_uncommitted_changes(reference_folder)
3402
- self.__sc.git_commit(build_repository_folder, "Updated submodules")
3403
-
3404
- if "--dependencyupdate" in generic_prepare_new_release_arguments.commandline_arguments:
3405
- GeneralUtilities.write_message_to_stdout("Debug: Update dependencies...")
3406
- self.generic_update_dependencies(repository_folder)
3407
- self.assert_no_uncommitted_changes(repository_folder)
3408
- else:
3409
- GeneralUtilities.write_message_to_stdout("Debug: Dependency-update skipped.")
3410
-
3411
- GeneralUtilities.write_message_to_stdout(f"Check reference-repository...")
3412
- now = GeneralUtilities.get_now()
3413
- for unsupported_version in self.get_unsupported_versions(repository_folder, now):
3414
- reference_folder = f"{reference_folder}/ReferenceContent/v{unsupported_version[0]}"
3415
- GeneralUtilities.ensure_directory_does_not_exist(reference_folder)
3416
- self.__sc.git_commit(reference_folder, "Removed reference of outdated versions.")
3417
-
3418
- merge_source_branch_commit_id = self.__sc.git_get_commit_id(repository_folder, merge_source_branch)
3419
- main_branch_commit_id = self.__sc.git_get_commit_id(repository_folder, main_branch)
3420
- if merge_source_branch_commit_id == main_branch_commit_id:
3421
- GeneralUtilities.write_message_to_stdout("Release will not be prepared because there are no changed which can be released.")
3422
- else:
3423
- self.merge_to_main_branch(repository_folder, merge_source_branch, verbosity=verbosity, fast_forward_source_branch=True)
3424
- self.__sc.git_commit(build_repository_folder, "Updated submodule due to merge to main-branch.")
3425
- GeneralUtilities.write_message_to_stdout(f"Finished prepare release for {generic_prepare_new_release_arguments.product_name}.")
3426
-
3427
- class GenericCreateReleaseArguments():
3428
- current_file: str
3429
- product_name: str
3430
- common_remote_name: str
3431
- artifacts_target_folder: str
3432
- commandline_arguments: list[str]
3433
-
3434
- def __init__(self, current_file: str, product_name: str, common_remote_name: str, artifacts_target_folder: str, commandline_arguments: list[str]):
3435
- self.current_file = current_file
3436
- self.product_name = product_name
3437
- self.common_remote_name = common_remote_name
3438
- self.artifacts_target_folder = artifacts_target_folder
3439
- self.commandline_arguments = commandline_arguments
3440
-
3441
- @GeneralUtilities.check_arguments
3442
- def generic_create_release(self, generic_create_release_arguments: GenericCreateReleaseArguments) -> tuple[bool, str]:
3443
- GeneralUtilities.write_message_to_stdout(f"Create release for {generic_create_release_arguments.product_name}.")
3444
- folder_of_this_file = os.path.dirname(generic_create_release_arguments.current_file)
3445
- build_repository_folder = GeneralUtilities.resolve_relative_path("../..", folder_of_this_file)
3446
- repository_folder_name = generic_create_release_arguments.product_name
3447
- repository_folder = GeneralUtilities.resolve_relative_path(f"../../Submodules/{generic_create_release_arguments.product_name}", folder_of_this_file)
3448
- self.__sc.assert_is_git_repository(repository_folder)
3449
-
3450
- merge_source_branch = "main" # TODO make this configurable
3451
- main_branch = "stable" # TODO make this configurable
3452
-
3453
- additional_arguments_file = os.path.join(folder_of_this_file, "AdditionalArguments.configuration")
3454
- verbosity: int = TasksForCommonProjectStructure.get_verbosity_from_commandline_arguments(generic_create_release_arguments.commandline_arguments, 1)
3455
- createReleaseConfiguration: CreateReleaseConfiguration = CreateReleaseConfiguration(generic_create_release_arguments.product_name, generic_create_release_arguments.common_remote_name, generic_create_release_arguments.artifacts_target_folder, folder_of_this_file, verbosity, repository_folder, additional_arguments_file, repository_folder_name)
3456
-
3457
- merge_source_branch_commit_id = self.__sc.git_get_commit_id(repository_folder, merge_source_branch)
3458
- main_branch_commit_id = self.__sc.git_get_commit_id(repository_folder, main_branch)
3459
- if merge_source_branch_commit_id == main_branch_commit_id:
3460
- GeneralUtilities.write_message_to_stdout("Release will not be done because there are no changed which can be released.")
3461
- return False, None
3462
- else:
3463
- self.__sc.git_checkout(repository_folder, merge_source_branch)
3464
- reference_repo: str = os.path.join(build_repository_folder, "Submodules", f"{generic_create_release_arguments.product_name}Reference")
3465
- self.__sc.git_commit(reference_repo, "Updated reference")
3466
- self.__sc.git_push_with_retry(reference_repo, generic_create_release_arguments.common_remote_name, "main", "main")
3467
- self.__sc.git_commit(build_repository_folder, "Updated submodule")
3468
-
3469
- # create release
3470
- new_version = self.merge_to_stable_branch(generic_create_release_arguments.current_file, createReleaseConfiguration)
3471
- GeneralUtilities.write_message_to_stdout(f"Finished create release for {generic_create_release_arguments.product_name}.")
3472
- return True, new_version
3473
-
3474
- class UpdateHTTPDocumentationArguments:
3475
- current_file: str
3476
- product_name: str
3477
- common_remote_name: str
3478
- new_project_version: str
3479
- reference_repository_name: str
3480
- commandline_arguments: list[str]
3481
- main_branch_name: str
3482
-
3483
- def __init__(self, current_file: str, product_name: str, common_remote_name: str, new_project_version: str, reference_repository_name: str, commandline_arguments: list[str]):
3484
- self.current_file = current_file
3485
- self.product_name = product_name
3486
- self.common_remote_name = common_remote_name
3487
- self.new_project_version = new_project_version
3488
- self.reference_repository_name = reference_repository_name
3489
- self.commandline_arguments = commandline_arguments
3490
- self.main_branch_name = "main"
3491
-
3492
- @GeneralUtilities.check_arguments
3493
- def create_changelog_entry(self, repositoryfolder: str, message: str, commit: bool, force: bool):
3494
- self.__sc.assert_is_git_repository(repositoryfolder)
3495
- random_file = os.path.join(repositoryfolder, str(uuid.uuid4()))
3496
- if force and not self.__sc.git_repository_has_uncommitted_changes(repositoryfolder):
3497
- GeneralUtilities.ensure_file_exists(random_file)
3498
- current_version = self.get_version_of_project(repositoryfolder)
3499
- changelog_file = os.path.join(repositoryfolder, "Other", "Resources", "Changelog", f"v{current_version}.md")
3500
- if os.path.isfile(changelog_file):
3501
- GeneralUtilities.write_message_to_stdout(f"Changelog-file '{changelog_file}' already exists.")
3502
- else:
3503
- GeneralUtilities.ensure_file_exists(changelog_file)
3504
- GeneralUtilities.write_text_to_file(changelog_file, f"""# Release notes
3505
-
3506
- ## Changes
3507
-
3508
- - {message}
3509
- """)
3510
- GeneralUtilities.ensure_file_does_not_exist(random_file)
3511
- if commit:
3512
- self.__sc.git_commit(repositoryfolder, f"Added changelog-file for v{current_version}.")
3513
-
3514
- @GeneralUtilities.check_arguments
3515
- def update_http_documentation(self, update_http_documentation_arguments: UpdateHTTPDocumentationArguments):
3516
- GeneralUtilities.write_message_to_stdout(f"Update HTTP-documentation for for {update_http_documentation_arguments.product_name}...")
3517
- folder_of_this_file = str(os.path.dirname(update_http_documentation_arguments.current_file))
3518
-
3519
- ref_repo = GeneralUtilities.resolve_relative_path(f"../../Submodules/{update_http_documentation_arguments.reference_repository_name}", folder_of_this_file)
3520
- self.__sc.assert_is_git_repository(ref_repo)
3521
- self.__sc.git_checkout(ref_repo, update_http_documentation_arguments.main_branch_name)
3522
-
3523
- # update reference
3524
- target = os.path.join(ref_repo, "Reference", update_http_documentation_arguments.product_name)
3525
- GeneralUtilities.ensure_directory_does_not_exist(target)
3526
- shutil.copytree(GeneralUtilities.resolve_relative_path(f"../../Submodules/{update_http_documentation_arguments.product_name}Reference/ReferenceContent", folder_of_this_file), target)
3527
- self.__sc.git_commit(ref_repo, f"Added reference of {update_http_documentation_arguments.product_name} v{update_http_documentation_arguments.new_project_version}")
3528
-
3529
- # Sync reference-repository
3530
- self.__sc.git_fetch(ref_repo, update_http_documentation_arguments.common_remote_name)
3531
- self.__sc.git_merge(ref_repo, update_http_documentation_arguments.common_remote_name+"/"+update_http_documentation_arguments.main_branch_name, update_http_documentation_arguments.main_branch_name)
3532
- self.__sc.git_checkout(ref_repo, update_http_documentation_arguments.main_branch_name)
3533
- self.__sc.git_push_with_retry(ref_repo, update_http_documentation_arguments.common_remote_name, update_http_documentation_arguments.main_branch_name, update_http_documentation_arguments.main_branch_name)
3534
- self.__sc.git_commit(GeneralUtilities.resolve_relative_path("../..", folder_of_this_file), f"Updated content of {update_http_documentation_arguments.product_name} v{update_http_documentation_arguments.new_project_version} in {update_http_documentation_arguments.reference_repository_name}-submodule")
3535
-
3536
- @GeneralUtilities.check_arguments
3537
- def install_requirementstxt_for_codeunit(self, codeunit_folder: str, verbosity: int):
3538
- self.__sc.install_requirementstxt_file(codeunit_folder+"/Other/requirements.txt", verbosity)
3539
-
3540
- @GeneralUtilities.check_arguments
3541
- def install_requirementstxt_for_repository(self, repository_folde: str, verbosity: int):
3542
- self.__sc.install_requirementstxt_file(repository_folde+"/Other/requirements.txt", verbosity)
3543
-
3544
- @GeneralUtilities.check_arguments
3545
- def update_submodule(self, repository_folder: str, submodule_name: str, local_branch: str = "main", remote_branch: str = "main", remote: str = "origin"):
3546
- submodule_folder = GeneralUtilities.resolve_relative_path("Other/Resources/Submodules/"+submodule_name, repository_folder)
3547
- self.__sc.git_fetch(submodule_folder, remote)
3548
- self.__sc.git_checkout(submodule_folder, local_branch)
3549
- self.__sc.git_pull(submodule_folder, remote, local_branch, remote_branch, True)
3550
- current_version = self.__sc.get_semver_version_from_gitversion(repository_folder)
3551
- changelog_file = os.path.join(repository_folder, "Other", "Resources", "Changelog", f"v{current_version}.md")
3552
- if (not os.path.isfile(changelog_file)):
3553
- GeneralUtilities.ensure_file_exists(changelog_file)
3554
- GeneralUtilities.write_text_to_file(changelog_file, """# Release notes
3555
-
3556
- ## Changes
3557
-
3558
- - Updated geo-ip-database.
3559
- """)
3560
-
3561
- @GeneralUtilities.check_arguments
3562
- def update_images_in_example(self, codeunit_folder: str):
3563
- iu = ImageUpdater()
3564
- iu.add_default_mapper()
3565
- dockercomposefile: str = f"{codeunit_folder}\\Other\\Reference\\ReferenceContent\\Examples\\MinimalDockerComposeFile\\docker-compose.yml"
3566
- excluded = ["opendms"]
3567
- iu.update_all_services_in_docker_compose_file(dockercomposefile, VersionEcholon.LatestPatchOrLatestMinor, excluded)
3568
- iu.check_for_newest_version(dockercomposefile, excluded)
3569
-
3570
- @GeneralUtilities.check_arguments
3571
- def clone_repository_as_resource(self, local_repository_folder: str, remote_repository_link: str, resource_name: str, repository_subname: str = None) -> None:
3572
- GeneralUtilities.write_message_to_stdout(f'Clone resource {resource_name}...')
3573
- resrepo_commit_id_folder: str = os.path.join(local_repository_folder, "Other", "Resources", f"{resource_name}Version")
3574
- resrepo_commit_id_file: str = os.path.join(resrepo_commit_id_folder, f"{resource_name}Version.txt")
3575
- latest_version: str = GeneralUtilities.read_text_from_file(resrepo_commit_id_file)
3576
- resrepo_data_folder: str = os.path.join(local_repository_folder, "Other", "Resources", resource_name).replace("\\", "/")
3577
- current_version: str = None
3578
- resrepo_data_version: str = os.path.join(resrepo_data_folder, f"{resource_name}Version.txt")
3579
- if os.path.isdir(resrepo_data_folder):
3580
- if os.path.isfile(resrepo_data_version):
3581
- current_version = GeneralUtilities.read_text_from_file(resrepo_data_version)
3582
- if (current_version is None) or (current_version != latest_version):
3583
- target_folder: str = resrepo_data_folder
3584
- if repository_subname is not None:
3585
- target_folder = f"{resrepo_data_folder}/{repository_subname}"
3586
- GeneralUtilities.ensure_folder_exists_and_is_empty(target_folder)
3587
- self.__sc.run_program("git", f"clone --recurse-submodules {remote_repository_link} {target_folder}")
3588
- self.__sc.run_program("git", f"checkout {latest_version}", target_folder)
3589
- GeneralUtilities.write_text_to_file(resrepo_data_version, latest_version)
3590
-
3591
- git_folders: list[str] = []
3592
- git_files: list[str] = []
3593
- for dirpath, dirnames, filenames in os.walk(target_folder):
3594
- for dirname in dirnames:
3595
- if dirname == ".git":
3596
- full_path = os.path.join(dirpath, dirname)
3597
- git_folders.append(full_path)
3598
- for filename in filenames:
3599
- if filename == ".git":
3600
- full_path = os.path.join(dirpath, filename)
3601
- git_files.append(full_path)
3602
- for git_folder in git_folders:
3603
- if os.path.isdir(git_folder):
3604
- GeneralUtilities.ensure_directory_does_not_exist(git_folder)
3605
- for git_file in git_files:
3606
- if os.path.isdir(git_file):
3607
- GeneralUtilities.ensure_file_does_not_exist(git_file)
3608
-
3609
- def set_latest_version_for_clone_repository_as_resource(self, resourcename: str, github_link: str, branch: str = "main"):
3610
- current_file = str(Path(__file__).absolute())
3611
- repository_folder = GeneralUtilities.resolve_relative_path("../../..", current_file)
3612
-
3613
- resrepo_commit_id_folder: str = os.path.join(repository_folder, "Other", "Resources", f"{resourcename}Version")
3614
- resrepo_commit_id_file: str = os.path.join(resrepo_commit_id_folder, f"{resourcename}Version.txt")
3615
- current_version: str = GeneralUtilities.read_text_from_file(resrepo_commit_id_file)
3616
-
3617
- stdOut = [l.split("\t") for l in GeneralUtilities.string_to_lines(self.__sc.run_program("git", f"ls-remote {github_link}")[1])]
3618
- stdOut = [l for l in stdOut if l[1] == f"refs/heads/{branch}"]
3619
- GeneralUtilities.assert_condition(len(stdOut) == 1)
3620
- latest_version: str = stdOut[0][0]
3621
- if current_version != latest_version:
3622
- GeneralUtilities.write_text_to_file(resrepo_commit_id_file, latest_version)