Coverage for gpkit\build.py: 0%
142 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-05 22:34 -0500
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-05 22:34 -0500
1"Finds solvers, sets gpkit settings, and builds gpkit"
2import os
3import sys
4import shutil
5import subprocess
7# pylint: disable=import-outside-toplevel # imports in try/catch statements
8# pylint: disable=too-few-public-methods
10LOGSTR = ""
11settings = {}
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"
21def pathjoin(*args):
22 "Join paths, collating multiple arguments."
23 return os.sep.join(args)
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
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
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
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)
72class SolverBackend:
73 "Inheritable class for finding solvers. Logs."
74 name = look = None
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
87class MosekCLI(SolverBackend):
88 "MOSEK command line interface finder."
89 name = "mosek_cli"
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
99 log("# Looks like `mskexpopt` was not found in the default PATH,")
100 log("# so let's try locating that binary ourselves.")
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}")
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}")
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")
134 return self.run("in " + bin_dir)
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
146class CVXopt(SolverBackend):
147 "CVXopt finder."
148 name = "cvxopt"
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 ""
160class MosekConif(SolverBackend):
161 "MOSEK exponential cone solver finder."
162 name = "mosek_conif"
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 ""
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])
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"])
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)
205 os.chdir(start_dir)
207if __name__ == "__main__":
208 build()