6161IS_LATEST = "--latest" in sys .argv
6262DELETE_DOCKERFILES = "--delete-dockerfiles" in sys .argv
6363DELETE_TEST_CLASSES = "--delete-test-classes" in sys .argv
64+ CUSTOM_FLAVOR = "--custom-flavor" in sys .argv
6465
6566# Release args management
6667if RELEASE is True :
114115 f"{ REPO_HOME } /megalinter/descriptors/schemas/megalinter-descriptor.jsonschema.json"
115116)
116117CONFIG_JSON_SCHEMA = f"{ REPO_HOME } /megalinter/descriptors/schemas/megalinter-configuration.jsonschema.json"
118+ CUSTOM_FLAVOR_JSON_SCHEMA = f"{ REPO_HOME } /megalinter/descriptors/schemas/megalinter-custom-flavor.jsonschema.json"
117119OWN_MEGALINTER_CONFIG_FILE = f"{ REPO_HOME } /.mega-linter.yml"
118120
119121IDE_LIST = {
@@ -252,12 +254,13 @@ def generate_flavor(flavor, flavor_info):
252254 json .dump (flavor_info , outfile , indent = 4 , sort_keys = True )
253255 outfile .write ("\n " )
254256 # Write in global flavors files
255- with open (GLOBAL_FLAVORS_FILE , "r" , encoding = "utf-8" ) as json_file :
256- global_flavors = json .load (json_file )
257- global_flavors [flavor ] = flavor_info
258- with open (GLOBAL_FLAVORS_FILE , "w" , encoding = "utf-8" ) as outfile :
259- json .dump (global_flavors , outfile , indent = 4 , sort_keys = True )
260- outfile .write ("\n " )
257+ if CUSTOM_FLAVOR is not True or os .path .isdir ("/megalinter-builder" ):
258+ with open (GLOBAL_FLAVORS_FILE , "r" , encoding = "utf-8" ) as json_file :
259+ global_flavors = json .load (json_file )
260+ global_flavors [flavor ] = flavor_info
261+ with open (GLOBAL_FLAVORS_FILE , "w" , encoding = "utf-8" ) as outfile :
262+ json .dump (global_flavors , outfile , indent = 4 , sort_keys = True )
263+ outfile .write ("\n " )
261264 # Flavored dockerfile
262265 dockerfile = f"{ FLAVORS_DIR } /{ flavor } /Dockerfile"
263266 if not os .path .isdir (os .path .dirname (dockerfile )):
@@ -302,7 +305,27 @@ def generate_flavor(flavor, flavor_info):
302305 with open (flavor_action_yml , "w" , encoding = "utf-8" ) as file :
303306 file .write (action_yml )
304307 logging .info (f"Updated { flavor_action_yml } " )
305- extra_lines = [
308+ extra_lines = []
309+ if CUSTOM_FLAVOR is True :
310+ current_date_time_iso = datetime .now ().isoformat ()
311+ extra_lines += [
312+ "ENV CUSTOM_FLAVOR=true \\ " ,
313+ f" BUILD_VERSION={ os .getenv ('BUILD_VERSION' , 'local_build' )} \\ " ,
314+ f" BUILD_DATE={ os .getenv ('BUILD_DATE' , 'local_build' )} \\ " ,
315+ f" BUILD_REVISION={ os .getenv ('BUILD_REVISION' , 'local_build' )} \\ " ,
316+ f" CUSTOM_FLAVOR_BUILD_DATE={ current_date_time_iso } \\ " ,
317+ f" CUSTOM_FLAVOR_BUILD_REPO={ os .getenv ('CUSTOM_FLAVOR_BUILD_REPO' , 'local_build' )} \\ " ,
318+ f" CUSTOM_FLAVOR_BUILD_REPO_URL={ os .getenv ('CUSTOM_FLAVOR_BUILD_REPO_URL' , 'local_build' )} \\ " ,
319+ f" CUSTOM_FLAVOR_BUILD_USER={ os .getenv ('CUSTOM_FLAVOR_BUILD_USER' , 'local_build' )} " ,
320+ "" ,
321+ 'LABEL com.github.actions.name="MegaLinter Custom Flavor" \\ ' ,
322+ f' maintainer="{ os .getenv ("CUSTOM_FLAVOR_BUILD_USER" , "local_build" )} " \\ ' ,
323+ f' org.opencontainers.image.source="{ os .getenv ("CUSTOM_FLAVOR_BUILD_REPO_URL" , "local_build" )} " \\ ' ,
324+ f' org.opencontainers.image.created="{ os .getenv ("BUILD_DATE" , "local_build" )} " \\ ' ,
325+ f' org.opencontainers.image.revision="{ os .getenv ("BUILD_REVISION" , "local_build" )} " \\ ' ,
326+ f' org.opencontainers.image.version="{ os .getenv ("BUILD_VERSION" , "local_build" )} "' ,
327+ ]
328+ extra_lines += [
306329 "COPY entrypoint.sh /entrypoint.sh" ,
307330 "RUN chmod +x entrypoint.sh" ,
308331 'ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]' ,
@@ -316,6 +339,7 @@ def generate_flavor(flavor, flavor_info):
316339 DEFAULT_DOCKERFILE_FLAVOR_ARGS .copy (),
317340 {"cargo" : DEFAULT_DOCKERFILE_FLAVOR_CARGO_PACKAGES .copy ()},
318341 )
342+ return dockerfile
319343
320344
321345def build_dockerfile (
@@ -689,6 +713,20 @@ def match_flavor(item, flavor, flavor_info):
689713 return True
690714 else :
691715 return False
716+ # Custom flavor
717+ elif flavor == "CUSTOM" :
718+ descriptors , linters_by_type = list_descriptors_for_build ()
719+ # Item is a linter: check if present in the flavor
720+ if "linter_name" in item and item ["name" ] in flavor_info ["linters" ]:
721+ return True
722+ # Item is a descriptor and it contains one of the linters included in the flavor info
723+ if "linters" in item :
724+ for descriptor in descriptors :
725+ if item ["descriptor_id" ] == descriptor ["descriptor_id" ]:
726+ descriptor_linters = descriptor ["linter_instances" ]
727+ for descriptor_linter in descriptor_linters :
728+ if descriptor_linter .name in flavor_info ["linters" ]:
729+ return True
692730 # Other flavors
693731 elif "descriptor_flavors" in item :
694732 if flavor in item ["descriptor_flavors" ] or (
@@ -860,10 +898,11 @@ def list_descriptors_for_build():
860898 descriptors = []
861899 for descriptor_file in descriptor_files :
862900 descriptor = megalinter .linter_factory .build_descriptor_info (descriptor_file )
863- descriptors += [descriptor ]
864901 descriptor_linters = megalinter .linter_factory .build_descriptor_linters (
865902 descriptor_file , {"request_id" : "build" }
866903 )
904+ descriptor ["linter_instances" ] = descriptor_linters
905+ descriptors += [descriptor ]
867906 linters_by_type [descriptor_linters [0 ].descriptor_type ] += descriptor_linters
868907 DESCRIPTORS_FOR_BUILD_CACHE = descriptors , linters_by_type
869908 return descriptors , linters_by_type
@@ -2855,6 +2894,15 @@ def generate_json_schema_enums():
28552894 with open (CONFIG_JSON_SCHEMA , "w" , encoding = "utf-8" ) as outfile :
28562895 json .dump (json_schema , outfile , indent = 2 , sort_keys = True )
28572896 outfile .write ("\n " )
2897+ # Also update megalinter custom flavor schema
2898+ with open (CUSTOM_FLAVOR_JSON_SCHEMA , "r" , encoding = "utf-8" ) as json_flavor_file :
2899+ json_flavor_schema = json .load (json_flavor_file )
2900+ json_flavor_schema ["definitions" ]["enum_linter_keys" ]["enum" ] = json_schema [
2901+ "definitions"
2902+ ]["enum_linter_keys" ]["enum" ]
2903+ with open (CUSTOM_FLAVOR_JSON_SCHEMA , "w" , encoding = "utf-8" ) as outfile_flavor :
2904+ json .dump (json_flavor_schema , outfile_flavor , indent = 2 , sort_keys = True )
2905+ outfile_flavor .write ("\n " )
28582906
28592907
28602908# Collect linters info from linter url, later used to build link preview card within linter documentation
@@ -3491,6 +3539,74 @@ def update_workflow_linters(file_path, linters):
34913539 f .write (file_content )
34923540
34933541
3542+ def generate_custom_flavor ():
3543+ megalinter_dir = (
3544+ "/megalinter-builder"
3545+ if os .path .isdir ("/megalinter-builder" )
3546+ else f"{ REPO_HOME } /.automation/test"
3547+ )
3548+ work_dir = (
3549+ "/github/workspace" if os .path .isdir ("/github/workspace" ) else megalinter_dir
3550+ )
3551+ reports_dir = (
3552+ f"{ work_dir } /megalinter-reports"
3553+ if work_dir == "/github/workspace"
3554+ else f"{ REPO_HOME } /megalinter-reports"
3555+ )
3556+ flavor_file = f"{ work_dir } /megalinter-custom-flavor.yml"
3557+ with open (flavor_file , "r" , encoding = "utf-8" ) as f :
3558+ flavor_info = yaml .safe_load (f )
3559+ flavor_info ["strict" ] = True
3560+ logging .info (f"Generating custom flavor from { flavor_file } in { megalinter_dir } " )
3561+ dockerfile_tmp = generate_flavor ("CUSTOM" , flavor_info )
3562+ dockerfile = f"{ megalinter_dir } /Dockerfile-megalinter-custom"
3563+ copyfile (dockerfile_tmp , dockerfile )
3564+ # Copy to reports dir
3565+ if not os .path .isdir (reports_dir ):
3566+ os .makedirs (reports_dir , exist_ok = True )
3567+ shutil .copyfile (dockerfile , f"{ reports_dir } /Dockerfile-megalinter-custom" )
3568+ # Delete folder containing dockerfile if runned locally
3569+ dockerfile_tmp_dir = os .path .dirname (dockerfile_tmp )
3570+ if os .path .isdir (dockerfile_tmp_dir ) and "/.automation/test" in work_dir :
3571+ logging .info (
3572+ f"Deleting folder { dockerfile_tmp_dir } containing custom flavor dockerfile"
3573+ )
3574+ shutil .rmtree (dockerfile_tmp_dir , ignore_errors = True )
3575+ # Display dockerfile content in log
3576+ with open (dockerfile , "r" , encoding = "utf-8" ) as f :
3577+ dockerfile_content = f .read ()
3578+ logging .info (f"Generated custom flavor dockerfile:\n \n { dockerfile_content } \n " )
3579+ return dockerfile
3580+
3581+
3582+ def build_custom_flavor (dockerfile ):
3583+ logging .info ("Building custom flavor docker image…" )
3584+ work_dir = (
3585+ "/megalinter-builder" if os .path .isdir ("/megalinter-builder" ) else REPO_HOME
3586+ )
3587+ tag_id = os .getenv ("CUSTOM_FLAVOR_BUILD_REPO" , "megalinter-custom" ).replace (
3588+ "/" , "_"
3589+ )
3590+ command = [
3591+ "docker" ,
3592+ "build" ,
3593+ "-t" ,
3594+ tag_id ,
3595+ "-f" ,
3596+ dockerfile ,
3597+ work_dir ,
3598+ ]
3599+ logging .info ("Running command: " + " " .join (command ))
3600+ process = subprocess .run (
3601+ command ,
3602+ stdout = subprocess .PIPE ,
3603+ stderr = subprocess .STDOUT ,
3604+ universal_newlines = True ,
3605+ )
3606+ stdout = utils .clean_string (process .stdout )
3607+ logging .info (f"Build custom flavor results: ({ process .returncode } )\n " + stdout )
3608+
3609+
34943610if __name__ == "__main__" :
34953611 logging_format = (
34963612 "[%(levelname)s] %(message)s"
@@ -3511,25 +3627,29 @@ def update_workflow_linters(file_path, linters):
35113627 handlers = [logging .StreamHandler (sys .stdout )],
35123628 )
35133629 config .init_config ("build" )
3514- # noinspection PyTypeChecker
3515- collect_linter_previews ()
3516- generate_json_schema_enums ()
3517- validate_descriptors ()
3518- if UPDATE_DEPENDENTS is True :
3519- update_dependents_info ()
3520- generate_all_flavors ()
3521- generate_linter_dockerfiles ()
3522- generate_linter_test_classes ()
3523- update_workflows_linters ()
3524- if UPDATE_DOC is True :
3525- logging .info ("Running documentation generators…" )
3526- # refresh_users_info() # deprecated since now we use github-dependents-info
3527- generate_documentation ()
3528- generate_documentation_all_linters ()
3529- # generate_documentation_all_users() # deprecated since now we use github-dependents-info
3530- generate_mkdocs_yml ()
3531- validate_own_megalinter_config ()
3532- manage_output_variables ()
3533- reformat_markdown_tables ()
3534- if RELEASE is True :
3535- generate_version ()
3630+ if CUSTOM_FLAVOR is True :
3631+ dockerfile = generate_custom_flavor ()
3632+ build_custom_flavor (dockerfile )
3633+ else :
3634+ # noinspection PyTypeChecker
3635+ collect_linter_previews ()
3636+ generate_json_schema_enums ()
3637+ validate_descriptors ()
3638+ if UPDATE_DEPENDENTS is True :
3639+ update_dependents_info ()
3640+ generate_all_flavors ()
3641+ generate_linter_dockerfiles ()
3642+ generate_linter_test_classes ()
3643+ update_workflows_linters ()
3644+ if UPDATE_DOC is True :
3645+ logging .info ("Running documentation generators…" )
3646+ # refresh_users_info() # deprecated since now we use github-dependents-info
3647+ generate_documentation ()
3648+ generate_documentation_all_linters ()
3649+ # generate_documentation_all_users() # deprecated since now we use github-dependents-info
3650+ generate_mkdocs_yml ()
3651+ validate_own_megalinter_config ()
3652+ manage_output_variables ()
3653+ reformat_markdown_tables ()
3654+ if RELEASE is True :
3655+ generate_version ()
0 commit comments