Coverage for gpkit\build.py: 0%

142 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-05 22:28 -0500

1"Finds solvers, sets gpkit settings, and builds gpkit" 

2import os 

3import sys 

4import shutil 

5import subprocess 

6 

7# pylint: disable=import-outside-toplevel # imports in try/catch statements 

8# pylint: disable=too-few-public-methods 

9 

10LOGSTR = "" 

11settings = {} 

12 

13 

14def log(*args): 

15 "Print a line and append it to the log string." 

16 global LOGSTR # pylint: disable=global-statement 

17 print(*args) 

18 LOGSTR += " ".join(args) + "\n" 

19 

20 

21def pathjoin(*args): 

22 "Join paths, collating multiple arguments." 

23 return os.sep.join(args) 

24 

25 

26def isfile(path): 

27 "Returns true if there's a file at $path. Logs." 

28 if os.path.isfile(path): 

29 log(f"# Found {path}") 

30 return True 

31 log(f"# Could not find {path}") 

32 return False 

33 

34 

35def replacedir(path): 

36 "Replaces directory at $path. Logs." 

37 log("# Replacing directory", path) 

38 if os.path.isdir(path): 

39 shutil.rmtree(path) 

40 os.makedirs(path) 

41 return path 

42 

43 

44def call(cmd): 

45 "Calls subprocess. Logs." 

46 log(f"# Calling '{cmd}'") 

47 log("##") 

48 log("### CALL BEGINS") 

49 retcode = subprocess.call(cmd, shell=True) 

50 log("### CALL ENDS") 

51 log("##") 

52 return retcode 

53 

54 

55def diff(filename, diff_dict): 

56 "Applies a simple diff to a file. Logs." 

57 with open(filename, "r", encoding="UTF-8") as a: 

58 with open(filename+".new", "w", encoding="UTF-8") as b: 

59 for line_number, line in enumerate(a): 

60 if line[:-1].strip() in diff_dict: 

61 newline = diff_dict[line[:-1].strip()]+"\n" 

62 log(f"#\n# Change in {filename}" 

63 f" on line {line_number + 1}") 

64 log("# --", line[:-1][:70]) 

65 log("# ++", newline[:70]) 

66 b.write(newline) 

67 else: 

68 b.write(line) 

69 shutil.move(filename+".new", filename) 

70 

71 

72class SolverBackend: 

73 "Inheritable class for finding solvers. Logs." 

74 name = look = None 

75 

76 def __init__(self): 

77 log(f"\n# Looking for `{self.name}`") 

78 found_in = self.look() # pylint: disable=not-callable 

79 if found_in: 

80 log(f"\nFound {self.name} {found_in}") 

81 self.installed = True 

82 else: 

83 log("# Did not find\n#", self.name) 

84 self.installed = False 

85 

86 

87class MosekCLI(SolverBackend): 

88 "MOSEK command line interface finder." 

89 name = "mosek_cli" 

90 

91 def look(self): # pylint: disable=too-many-return-statements 

92 "Looks in default install locations for a mosek before version 9." 

93 log("# (A \"success\" is if mskexpopt complains that") 

94 log("# we haven't specified a file for it to open.)") 

95 already_in_path = self.run() 

96 if already_in_path: 

97 return already_in_path 

98 

99 log("# Looks like `mskexpopt` was not found in the default PATH,") 

100 log("# so let's try locating that binary ourselves.") 

101 

102 if sys.platform[:3] == "win": 

103 rootdir = "C:\\Program Files\\Mosek" 

104 mosek_platform = "win64x86" 

105 elif sys.platform[:6] == "darwin": 

106 rootdir = pathjoin(os.path.expanduser("~"), "mosek") 

107 mosek_platform = "osx64x86" 

108 elif sys.platform[:5] == "linux": 

109 rootdir = pathjoin(os.path.expanduser("~"), "mosek") 

110 mosek_platform = "linux64x86" 

111 else: 

112 return log(f"# Platform unsupported: {sys.platform}") 

113 

114 if "MSKHOME" in os.environ: # allow specification of root dir 

115 rootdir = os.environ["MSKHOME"] 

116 log(f"# Using MSKHOME environment variable (value {rootdir})" 

117 " instead of OS-default MOSEK home directory") 

118 if not os.path.isdir(rootdir): 

119 return log("# expected MOSEK directory not found: {rootdir}") 

120 

121 possible_versions = [f for f in os.listdir(rootdir) 

122 if len(f) == 1 and f < "9"] 

123 if not possible_versions: 

124 return log("# no version folders (e.g. '7', '8') found" 

125 f" in mosek directory \"{rootdir}\"") 

126 version = sorted(possible_versions)[-1] 

127 tools_dir = pathjoin(rootdir, version, "tools") 

128 lib_dir = pathjoin(tools_dir, "platform", mosek_platform) 

129 bin_dir = pathjoin(lib_dir, "bin") 

130 settings["mosek_bin_dir"] = bin_dir 

131 os.environ['PATH'] = os.environ['PATH'] + os.pathsep + bin_dir 

132 log(f"# Adding {bin_dir} to the PATH") 

133 

134 return self.run("in " + bin_dir) 

135 

136 def run(self, where="in the default PATH"): 

137 "Attempts to run mskexpopt." 

138 try: 

139 if call("mskexpopt") in (1052, 28): # 28 for MacOSX 

140 return where 

141 except: # pylint: disable=bare-except 

142 pass # exception type varies by operating system 

143 return None 

144 

145 

146class CVXopt(SolverBackend): 

147 "CVXopt finder." 

148 name = "cvxopt" 

149 

150 def look(self): 

151 "Attempts to import cvxopt." 

152 try: 

153 log("# Trying to import cvxopt...") 

154 import cvxopt # pylint: disable=unused-import 

155 return "in the default PYTHONPATH" 

156 except ImportError: 

157 return "" 

158 

159 

160class MosekConif(SolverBackend): 

161 "MOSEK exponential cone solver finder." 

162 name = "mosek_conif" 

163 

164 def look(self): 

165 "Attempts to import a mosek supporting exponential cones." 

166 try: 

167 log("# Trying to import mosek...") 

168 import mosek 

169 if hasattr(mosek.conetype, "pexp"): 

170 return "in the default PYTHONPATH" 

171 return "" 

172 except ImportError: 

173 return "" 

174 

175def build(): 

176 "Builds GPkit" 

177 import gpkit 

178 log(f"# Building GPkit version {gpkit.__version__}") 

179 log("# Moving to the directory from which GPkit was imported.") 

180 start_dir = os.getcwd() 

181 os.chdir(gpkit.__path__[0]) 

182 

183 log("\nAttempting to find and build solvers:") 

184 solvers = [MosekCLI(), MosekConif(), CVXopt()] 

185 installed_solvers = [solver.name for solver in solvers if solver.installed] 

186 if not installed_solvers: 

187 log("Can't find any solvers!\n") 

188 if "GPKITSOLVERS" in os.environ: 

189 log(f"Replaced found solvers ({installed_solvers}) with environment " 

190 f"var GPKITSOLVERS ({os.environ['GPKITSOLVERS']})") 

191 settings["installed_solvers"] = os.environ["GPKITSOLVERS"] 

192 else: 

193 settings["installed_solvers"] = ", ".join(installed_solvers) 

194 log("\nFound the following solvers: " + settings["installed_solvers"]) 

195 

196 # Write settings 

197 envpath = "env" 

198 replacedir(envpath) 

199 with open(pathjoin(envpath, "settings"), "w", encoding="UTF-8") as f: 

200 for setting, value in sorted(settings.items()): 

201 f.write(f"{setting} : {value}\n") 

202 with open(pathjoin(envpath, "build.log"), "w", encoding="UTF-8") as f: 

203 f.write(LOGSTR) 

204 

205 os.chdir(start_dir) 

206 

207if __name__ == "__main__": 

208 build()