Coverage for sparkle/CLI/add_solver.py: 83%

88 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-07 15:22 +0000

1#!/usr/bin/env python3 

2"""Sparkle command to add a solver to the Sparkle platform.""" 

3import os 

4import stat 

5import sys 

6import argparse 

7import shutil 

8from pathlib import Path 

9 

10from sparkle.platform import file_help as sfh 

11from sparkle.CLI.help import global_variables as gv 

12from sparkle.structures import PerformanceDataFrame 

13from sparkle.solver import Solver, verifiers 

14from sparkle.CLI.help import logging as sl 

15from sparkle.CLI.initialise import check_for_initialise 

16from sparkle.CLI.help import argparse_custom as ac 

17 

18 

19def parser_function() -> argparse.ArgumentParser: 

20 """Define the command line arguments.""" 

21 parser = argparse.ArgumentParser( 

22 description="Add a solver to the Sparkle platform.") 

23 parser.add_argument(*ac.DeterministicArgument.names, 

24 **ac.DeterministicArgument.kwargs) 

25 parser.add_argument(*ac.SolutionVerifierArgument.names, 

26 **ac.SolutionVerifierArgument.kwargs) 

27 parser.add_argument(*ac.NicknameSolverArgument.names, 

28 **ac.NicknameSolverArgument.kwargs) 

29 parser.add_argument(*ac.SolverPathArgument.names, **ac.SolverPathArgument.kwargs) 

30 parser.add_argument(*ac.SkipChecksArgument.names, **ac.SkipChecksArgument.kwargs) 

31 parser.add_argument(*ac.NoCopyArgument.names, **ac.NoCopyArgument.kwargs) 

32 return parser 

33 

34 

35def main(argv: list[str]) -> None: 

36 """Main function of the command.""" 

37 # Log command call 

38 sl.log_command(sys.argv) 

39 check_for_initialise() 

40 

41 # Define command line arguments 

42 parser = parser_function() 

43 

44 # Process command line arguments 

45 args = parser.parse_args(argv) 

46 solver_source = Path(args.solver_path) 

47 deterministic = args.deterministic 

48 solution_verifier = args.solution_verifier 

49 

50 if not solver_source.exists(): 

51 print(f'Solver path "{solver_source}" does not exist!') 

52 sys.exit(-1) 

53 

54 # Make sure it is pointing to the verifiers module 

55 if solution_verifier: 

56 if Path(solution_verifier).is_file(): # File verifier 

57 solution_verifier = (verifiers.SolutionFileVerifier.__name__, 

58 solution_verifier) 

59 elif solution_verifier not in verifiers.mapping: 

60 print(f"ERROR: Unknown solution verifier {solution_verifier}!") 

61 sys.exit(-1) 

62 

63 nickname = args.nickname 

64 

65 if args.run_checks: 

66 print("Running checks...") 

67 solver = Solver(Path(solver_source)) 

68 pcs_file = solver.get_pcs_file() 

69 if pcs_file is None: 

70 print("None or multiple .pcs files found. Solver " 

71 "is not valid for configuration.") 

72 else: 

73 print(f"One pcs file detected: {pcs_file.name}. ", end="") 

74 if solver.read_pcs_file(): 

75 print("Can read the pcs file.") 

76 else: 

77 print("WARNING: Can not read the provided pcs file format.") 

78 

79 configurator_wrapper_path = solver_source / Solver.wrapper 

80 if not (configurator_wrapper_path.is_file() 

81 and os.access(configurator_wrapper_path, os.X_OK)): 

82 print(f"WARNING: Solver {solver_source.name} does not have a solver wrapper " 

83 f"(Missing file {Solver.wrapper}) or is not executable. ") 

84 

85 # Start add solver 

86 solver_directory = gv.settings().DEFAULT_solver_dir / solver_source.name 

87 if solver_directory.exists(): 

88 print(f"ERROR: Solver {solver_source.name} already exists! " 

89 "Can not add new solver.") 

90 sys.exit(-1) 

91 if args.no_copy: 

92 print(f"Creating symbolic link from {solver_source} " 

93 f"to {solver_directory}...") 

94 if not os.access(solver_source, os.W_OK): 

95 raise PermissionError("Sparkle does not have the right to write to the " 

96 "destination folder.") 

97 solver_directory.symlink_to(solver_source.absolute()) 

98 else: 

99 print(f"Copying {solver_source.name} to platform...") 

100 solver_directory.mkdir(parents=True) 

101 shutil.copytree(solver_source, solver_directory, dirs_exist_ok=True) 

102 

103 # Save the deterministic bool in the solver 

104 with (solver_directory / Solver.meta_data).open("w+") as fout: 

105 fout.write(str({"deterministic": deterministic, 

106 "verifier": solution_verifier})) 

107 

108 # Add RunSolver executable to the solver 

109 runsolver_path = gv.settings().DEFAULT_runsolver_exec 

110 if runsolver_path.name in [file.name for file in solver_directory.iterdir()]: 

111 print("Warning! RunSolver executable detected in Solver " 

112 f"{solver_source.name}. This will be replaced with " 

113 f"Sparkle's version of RunSolver. ({runsolver_path})") 

114 runsolver_target = solver_directory / runsolver_path.name 

115 shutil.copyfile(runsolver_path, runsolver_target) 

116 runsolver_target.chmod(os.stat(runsolver_target).st_mode | stat.S_IEXEC) 

117 

118 performance_data = PerformanceDataFrame( 

119 gv.settings().DEFAULT_performance_data_path, 

120 objectives=gv.settings().get_general_sparkle_objectives()) 

121 performance_data.add_solver(str(solver_directory)) 

122 performance_data.save_csv() 

123 

124 print(f"Adding solver {solver_source.name} done!") 

125 

126 if nickname is not None: 

127 sfh.add_remove_platform_item( 

128 solver_directory, 

129 gv.solver_nickname_list_path, 

130 gv.file_storage_data_mapping[gv.solver_nickname_list_path], 

131 key=nickname) 

132 

133 solver = Solver(solver_directory) # Recreate solver from its new directory 

134 if solver.get_pcs_file() is not None: 

135 print("Generating missing PCS files...") 

136 solver.port_pcs("IRACE") # Create PCS file for IRACE 

137 print("Generating done!") 

138 

139 # Write used settings to file 

140 gv.settings().write_used_settings() 

141 sys.exit(0) 

142 

143 

144if __name__ == "__main__": 

145 main(sys.argv[1:])