66import stat
77import subprocess
88import sys
9+ import tempfile
910from typing import AnyStr , IO , Optional , Mapping
1011
1112# lib imports
2122# result placeholder
2223ERROR = False
2324FAILURES = []
24- TEMP_DIRECTORIES = []
25- HOMEBREW_BUILDPATH = ""
25+ HOMEBREW_TEMP_ENV_VAR = "HOMEBREW_TEMP"
26+ TEST_ARTIFACTS_ENV_VAR = "HOMEBREW_TEST_ARTIFACTS_DIR "
2627
2728tap_repo_name = "" # will be set based on INPUT_ORG_HOMEBREW_REPO
2829
2930og_dir = os .getcwd ()
3031
3132
3233def _parse_args (args_list : list ) -> argparse .Namespace :
33- parser = argparse .ArgumentParser (description = 'Homebrew formula audit, install , and test' )
34+ parser = argparse .ArgumentParser (description = 'Homebrew formula audit, bottle build , and test' )
3435 parser .add_argument (
3536 '--formula_file' ,
3637 default = os .environ ['INPUT_FORMULA_FILE' ],
37- help = 'Homebrew formula file to audit, install , and test' ,
38+ help = 'Homebrew formula file to audit, bottle build , and test' ,
3839 type = str ,
3940 )
4041 return parser .parse_args (args_list )
@@ -892,7 +893,137 @@ def brew_test_bot_only_tap_syntax() -> bool:
892893 return result
893894
894895
895- def brew_test_bot_only_formulae (formula : str ) -> bool :
896+ def get_test_artifacts_dir () -> str :
897+ """
898+ Get the action-owned directory where formulae can write test artifacts.
899+
900+ Returns
901+ -------
902+ str
903+ Absolute path to the test artifacts directory.
904+ """
905+ return os .path .abspath (
906+ os .path .join (
907+ os .environ ['GITHUB_WORKSPACE' ],
908+ 'release_homebrew_action' ,
909+ 'test' ,
910+ )
911+ )
912+
913+
914+ def get_homebrew_temp_dir () -> str :
915+ """
916+ Get the Homebrew temp root used by test-bot.
917+
918+ Returns
919+ -------
920+ str
921+ Absolute path to the Homebrew temp root.
922+ """
923+ return os .path .abspath (
924+ os .path .join (
925+ tempfile .gettempdir (),
926+ 'release_homebrew_action' ,
927+ )
928+ )
929+
930+
931+ def get_homebrew_test_artifacts_dir (formula : str ) -> str :
932+ """
933+ Get the formula temp directory where formulae can write during bottle builds.
934+
935+ Returns
936+ -------
937+ str
938+ Absolute path to the Homebrew-side test artifacts directory.
939+ """
940+ return os .path .abspath (
941+ os .path .join (
942+ get_homebrew_temp_dir (),
943+ formula ,
944+ 'test' ,
945+ )
946+ )
947+
948+
949+ def prepare_test_artifacts_dir (formula : str ) -> str :
950+ """
951+ Prepare an action-owned directory for test artifacts from the bottle build.
952+
953+ Parameters
954+ ----------
955+ formula : str
956+ Name of the Homebrew formula being validated.
957+
958+ Returns
959+ -------
960+ str
961+ Absolute path to the prepared test artifacts directory.
962+ """
963+ test_artifacts_dir = get_test_artifacts_dir ()
964+ action_work_dir = os .path .abspath (
965+ os .path .join (
966+ os .environ ['GITHUB_WORKSPACE' ],
967+ 'release_homebrew_action' ,
968+ )
969+ )
970+
971+ if os .path .commonpath ([action_work_dir , test_artifacts_dir ]) != action_work_dir :
972+ raise ValueError (f'::error:: Refusing to clean unexpected path { test_artifacts_dir } ' )
973+
974+ if os .path .isdir (test_artifacts_dir ):
975+ shutil .rmtree (test_artifacts_dir )
976+ elif os .path .exists (test_artifacts_dir ):
977+ os .remove (test_artifacts_dir )
978+
979+ homebrew_test_artifacts_dir = get_homebrew_test_artifacts_dir (formula )
980+ homebrew_artifacts_root = os .path .abspath (
981+ os .path .join (
982+ get_homebrew_temp_dir (),
983+ formula ,
984+ )
985+ )
986+
987+ if os .path .commonpath ([homebrew_artifacts_root , homebrew_test_artifacts_dir ]) != homebrew_artifacts_root :
988+ raise ValueError (f'::error:: Refusing to clean unexpected path { homebrew_test_artifacts_dir } ' )
989+
990+ if os .path .isdir (homebrew_test_artifacts_dir ):
991+ shutil .rmtree (homebrew_test_artifacts_dir )
992+ elif os .path .exists (homebrew_test_artifacts_dir ):
993+ os .remove (homebrew_test_artifacts_dir )
994+
995+ os .makedirs (test_artifacts_dir , exist_ok = True )
996+ os .makedirs (homebrew_test_artifacts_dir , exist_ok = True )
997+
998+ set_github_action_output (
999+ output_name = 'testpath' ,
1000+ output_value = test_artifacts_dir ,
1001+ )
1002+
1003+ return test_artifacts_dir
1004+
1005+
1006+ def copy_test_artifacts (source_dir : str , destination_dir : str ) -> None :
1007+ """
1008+ Copy test artifacts from Homebrew logs to the action output directory.
1009+
1010+ Parameters
1011+ ----------
1012+ source_dir : str
1013+ Directory where the formula wrote artifacts during the Homebrew build.
1014+ destination_dir : str
1015+ Directory exposed as the action's testpath output.
1016+ """
1017+ if not os .path .isdir (source_dir ):
1018+ print (f'No Homebrew test artifacts found at { source_dir } ' )
1019+ return
1020+
1021+ print (f'Copying Homebrew test artifacts from { source_dir } to { destination_dir } ' )
1022+ os .makedirs (destination_dir , exist_ok = True )
1023+ shutil .copytree (source_dir , destination_dir , dirs_exist_ok = True )
1024+
1025+
1026+ def brew_test_bot_only_formulae (formula : str , test_artifacts_dir : Optional [str ] = None ) -> bool :
8961027 start_group (f'Running brew test-bot --only-formulae for { formula } ' )
8971028
8981029 org_repo = os .environ ['INPUT_ORG_HOMEBREW_REPO' ]
@@ -922,113 +1053,24 @@ def brew_test_bot_only_formulae(formula: str) -> bool:
9221053 args_list .append ('--skip-livecheck' )
9231054 print ('Skipping livecheck (running from fork PR)' )
9241055
925- env = get_test_bot_env ({
1056+ homebrew_temp_dir = get_homebrew_temp_dir ()
1057+ homebrew_test_artifacts_dir = get_homebrew_test_artifacts_dir (formula )
1058+ extra_env = {
9261059 'HOMEBREW_BOTTLE_BUILD' : 'true' , # setting this will allow us to skip advanced tests when building bottles
1060+ HOMEBREW_TEMP_ENV_VAR : homebrew_temp_dir ,
9271061 'HOMEBREW_NO_ASK' : '1' , # do not prompt for confirmation
928- })
929-
930- result = _run_subprocess (
931- args_list = args_list ,
932- env = env ,
933- )
934-
935- end_group ()
936- return result
937-
938-
939- def find_tmp_dir (formula : str ) -> str :
940- print ('Trying to find temp directory' )
941- root_tmp_dirs = [
942- os .getenv ('HOMEBREW_TEMP' , "" ), # if manually set
943- '/private/tmp' , # macOS default
944- '/var/tmp' , # Linux default
945- ]
946-
947- # first tmp dir that exists
948- root_tmp_dir = next ((d for d in root_tmp_dirs if os .path .isdir (d )), None )
949-
950- if not root_tmp_dir :
951- raise FileNotFoundError ('::error:: Could not find root temp directory' )
952-
953- print (f'Using temp directory { root_tmp_dir } ' )
954-
955- # find formula temp directories not already in the list
956- for d in os .listdir (root_tmp_dir ):
957- print (f'Checking temp directory { d } ' )
958- tmp_dir = os .path .join (root_tmp_dir , d )
959- if d .startswith (f'{ formula } -' ) and tmp_dir not in TEMP_DIRECTORIES :
960- print (f'Found temp directory { tmp_dir } ' )
961- TEMP_DIRECTORIES .append (tmp_dir )
962- break
963- else :
964- tmp_dir = ""
965-
966- if not tmp_dir :
967- raise FileNotFoundError (f'::error:: Could not find temp directory { tmp_dir } ' )
968-
969- return tmp_dir
970-
971-
972- def install_formula (formula : str ) -> bool :
973- start_group (f'Installing formula { formula } ' )
974-
975- env = {
976- 'HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK' : '1' ,
977- }
978-
979- # combine with os environment
980- env .update (os .environ )
981-
982- result = _run_subprocess (
983- args_list = [
984- 'brew' ,
985- 'install' ,
986- '--build-from-source' ,
987- '--include-test' ,
988- '--keep-tmp' ,
989- '--verbose' ,
990- f'{ tap_repo_name } /{ formula } ' ,
991- ],
992- env = env ,
993- )
994-
995- global HOMEBREW_BUILDPATH
996- HOMEBREW_BUILDPATH = find_tmp_dir (formula )
997-
998- set_github_action_output (
999- output_name = 'buildpath' ,
1000- output_value = HOMEBREW_BUILDPATH
1001- )
1002-
1003- end_group ()
1004- return result
1005-
1006-
1007- def test_formula (formula : str ) -> bool :
1008- start_group (f'Testing formula { formula } ' )
1009-
1010- env = {
1011- 'HOMEBREW_BUILDPATH' : HOMEBREW_BUILDPATH ,
1062+ TEST_ARTIFACTS_ENV_VAR : homebrew_test_artifacts_dir ,
10121063 }
10131064
1014- # combine with os environment
1015- env .update (os .environ )
1065+ env = get_test_bot_env (extra_env )
10161066
10171067 result = _run_subprocess (
1018- args_list = [
1019- 'brew' ,
1020- 'test' ,
1021- '--keep-tmp' ,
1022- '--verbose' ,
1023- f'{ tap_repo_name } /{ formula } ' ,
1024- ],
1068+ args_list = args_list ,
10251069 env = env ,
10261070 )
10271071
1028- set_github_action_output (
1029- output_name = 'testpath' ,
1030- output_value = find_tmp_dir (formula )
1031- )
1072+ if test_artifacts_dir :
1073+ copy_test_artifacts (homebrew_test_artifacts_dir , test_artifacts_dir )
10321074
10331075 end_group ()
10341076 return result
@@ -1042,9 +1084,11 @@ def main():
10421084 formula = process_input_formula (args .formula_file )
10431085
10441086 if os .environ ['INPUT_VALIDATE' ].lower () != 'true' :
1045- print ('Skipping audit, install, and test ' )
1087+ print ('Skipping audit and bottle validation ' )
10461088 return
10471089
1090+ test_artifacts_dir = prepare_test_artifacts_dir (formula )
1091+
10481092 if not brew_test_bot_only_cleanup_before ():
10491093 print ('::error:: brew test-bot --only-cleanup-before failed' )
10501094 raise SystemExit (1 )
@@ -1073,15 +1117,7 @@ def main():
10731117 print ('::error:: brew test-bot --only-tap-syntax failed' )
10741118 FAILURES .append ('tap-syntax' )
10751119
1076- if not install_formula (formula ):
1077- print (f'::error:: Formula { formula } failed install' )
1078- FAILURES .append ('install' )
1079-
1080- if not test_formula (formula ):
1081- print (f'::error:: Formula { formula } failed test' )
1082- FAILURES .append ('test' )
1083-
1084- if not brew_test_bot_only_formulae (formula ):
1120+ if not brew_test_bot_only_formulae (formula , test_artifacts_dir ):
10851121 print ('::error:: brew test-bot --only-formulae failed' )
10861122 FAILURES .append ('formulae' )
10871123
0 commit comments