Look for CMake in homebrew.
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / general / cmake / CMakeUtils.java
blob7622724cca9bf08798be0e08e42563349d092162
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // Multi-Phasic Applications: SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc.squirreljme.plugin.general.cmake;
12 import cc.squirreljme.plugin.util.ForwardInputToOutput;
13 import cc.squirreljme.plugin.util.ForwardStream;
14 import cc.squirreljme.plugin.util.PathUtils;
15 import java.io.BufferedReader;
16 import java.io.ByteArrayOutputStream;
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.io.OutputStream;
20 import java.io.Reader;
21 import java.io.StringReader;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.nio.file.StandardOpenOption;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.concurrent.TimeUnit;
33 import org.gradle.api.logging.LogLevel;
34 import org.gradle.api.logging.Logger;
35 import org.gradle.internal.os.OperatingSystem;
36 import org.gradle.util.internal.VersionNumber;
38 /**
39 * Utilities for CMake.
41 * @since 2024/03/15
43 public final class CMakeUtils
45 /**
46 * Not used.
48 * @since 2024/03/15
50 private CMakeUtils()
54 /**
55 * Returns the path where CMake is available.
57 * @return The CMake path.
58 * @since 2024/03/15
60 public static Path cmakeExePath()
62 // Standard executable?
63 Path cmakePath = PathUtils.findPath("cmake");
65 // Windows executable?
66 if (cmakePath == null)
67 cmakePath = PathUtils.findPath("cmake.exe");
69 // Standard installation on Windows?
70 if (cmakePath == null &&
71 OperatingSystem.current() == OperatingSystem.WINDOWS)
73 String programFiles = System.getenv("PROGRAMFILES");
74 if (programFiles != null)
76 Path maybe = Paths.get(programFiles).resolve("CMake")
77 .resolve("bin").resolve("cmake.exe");
78 if (Files.exists(maybe))
79 cmakePath = maybe;
83 // Homebrew on macOS?
84 if (cmakePath == null &&
85 OperatingSystem.current() == OperatingSystem.MAC_OS)
87 Path maybe = Paths.get("/").resolve("opt")
88 .resolve("homebrew").resolve("bin")
89 .resolve("cmake");
90 if (Files.exists(maybe))
91 cmakePath = maybe;
94 return cmakePath;
97 /**
98 * Requests the version of CMake.
100 * @return The resultant CMake version or {@code null} if there is no
101 * CMake available.
102 * @since 2024/04/01
104 public static VersionNumber cmakeExeVersion()
106 // We need the CMake executable
107 Path cmakeExe = CMakeUtils.cmakeExePath();
108 if (cmakeExe == null)
109 return null;
113 String rawStr = CMakeUtils.cmakeExecuteOutput("version",
114 "--version");
116 // Read in what looks like a version number
117 try (BufferedReader buf = new BufferedReader(
118 new StringReader(rawStr)))
120 for (;;)
122 String ln = buf.readLine();
123 if (ln == null)
124 break;
126 // Remove any whitespace and make it lowercase so it is
127 // easier to parse
128 ln = ln.trim().toLowerCase(Locale.ROOT);
130 // Is this the version string?
131 if (ln.startsWith("cmake version"))
132 return VersionNumber.parse(
133 ln.substring("cmake version".length()).trim());
137 // Failed
138 throw new RuntimeException(
139 "CMake executed but there was no version.");
141 catch (IOException __e)
143 throw new RuntimeException("Could not determine CMake version.",
144 __e);
149 * Executes a CMake task.
151 * @param __logger The logger to use.
152 * @param __logName The name of the log.
153 * @param __logDir The CMake build directory.
154 * @param __args CMake arguments.
155 * @return The CMake exit value.
156 * @throws IOException On read/write or execution errors.
157 * @since 2024/03/15
159 public static int cmakeExecute(Logger __logger, String __logName,
160 Path __logDir, String... __args)
161 throws IOException
163 if (__logDir == null)
164 throw new NullPointerException("NARG");
166 // Output log files
167 Path outLog = __logDir.resolve(__logName + ".out");
168 Path errLog = __logDir.resolve(__logName + ".err");
170 // Make sure directories exist first
171 Files.createDirectories(outLog.getParent());
172 Files.createDirectories(errLog.getParent());
174 // Run with log wrapping
175 try (OutputStream stdOut = Files.newOutputStream(outLog,
176 StandardOpenOption.CREATE, StandardOpenOption.WRITE,
177 StandardOpenOption.TRUNCATE_EXISTING);
178 OutputStream stdErr = Files.newOutputStream(errLog,
179 StandardOpenOption.CREATE, StandardOpenOption.WRITE,
180 StandardOpenOption.TRUNCATE_EXISTING))
182 // Execute CMake
183 return CMakeUtils.cmakeExecutePipe(null, stdOut, stdErr,
184 __logName, __args);
187 // Dump logs to Gradle
188 finally
190 CMakeUtils.dumpLog(__logger,
191 LogLevel.LIFECYCLE, outLog);
192 CMakeUtils.dumpLog(__logger,
193 LogLevel.ERROR, errLog);
198 * Executes a CMake task and returns the output as a string.
200 * @param __buildType The build type used.
201 * @param __args CMake arguments.
202 * @throws IOException On read/write or execution errors.
203 * @since 2024/04/01
205 public static String cmakeExecuteOutput(String __buildType,
206 String... __args)
207 throws IOException
209 try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
211 // Run command
212 CMakeUtils.cmakeExecutePipe(null, baos, null,
213 __buildType, __args);
215 // Decode to output
216 return baos.toString("utf-8");
221 * Executes a CMake task to the given pipe.
223 * @param __in The standard input for the task.
224 * @param __out The standard output for the task.
225 * @param __err The standard error for the task.
226 * @param __buildType The build type used.
227 * @param __args CMake arguments.
228 * @return The CMake exit value.
229 * @throws IOException On read/write or execution errors.
230 * @since 2024/04/01
232 public static int cmakeExecutePipe(InputStream __in,
233 OutputStream __out, OutputStream __err, String __buildType,
234 String... __args)
235 throws IOException
237 return CMakeUtils.cmakeExecutePipe(true, __in, __out, __err,
238 __buildType, __args);
242 * Executes a CMake task to the given pipe.
244 * @param __fail Emit failure if exit status is non-zero.
245 * @param __in The standard input for the task.
246 * @param __out The standard output for the task.
247 * @param __err The standard error for the task.
248 * @param __buildType The build type used.
249 * @param __args CMake arguments.
250 * @return The CMake exit value.
251 * @throws IOException On read/write or execution errors.
252 * @since 2024/04/01
254 public static int cmakeExecutePipe(boolean __fail, InputStream __in,
255 OutputStream __out, OutputStream __err, String __buildType,
256 String... __args)
257 throws IOException
259 // Need CMake
260 Path cmakePath = CMakeUtils.cmakeExePath();
261 if (cmakePath == null)
262 throw new RuntimeException("CMake not found.");
264 // Determine run arguments
265 List<String> args = new ArrayList<>();
266 args.add(cmakePath.toAbsolutePath().toString());
267 if (__args != null && __args.length > 0)
268 args.addAll(Arrays.asList(__args));
270 // Set executable process
271 ProcessBuilder procBuilder = new ProcessBuilder();
272 procBuilder.command(args);
274 // Log the output somewhere
275 if (__in != null)
276 procBuilder.redirectInput(ProcessBuilder.Redirect.PIPE);
277 if (__out != null)
278 procBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE);
279 if (__err != null)
280 procBuilder.redirectError(ProcessBuilder.Redirect.PIPE);
282 // Start the process
283 Process proc = procBuilder.start();
285 // Wait for it to complete
286 try (ForwardStream fwdOut = (__out == null ? null :
287 new ForwardInputToOutput(proc.getInputStream(), __out));
288 ForwardStream fwdErr = (__err == null ? null :
289 new ForwardInputToOutput(proc.getErrorStream(), __err)))
291 // Forward output
292 if (__out != null)
293 fwdOut.runThread("cmake-stdout");
295 // Forward error
296 if (__err != null)
297 fwdErr.runThread("cmake-stderr");
299 // Wait for completion, stop if it takes too long
300 if (!proc.waitFor(15, TimeUnit.MINUTES) ||
301 (__fail && proc.exitValue() != 0))
302 throw new RuntimeException(String.format(
303 "CMake failed to %s...", __buildType));
305 // Use the given exit value
306 return proc.exitValue();
308 catch (InterruptedException __e)
310 throw new RuntimeException("CMake timed out!", __e);
312 finally
314 // Destroy the task
315 proc.destroy();
320 * Dumps log to the output.
322 * @param __logger The logger to output to.
323 * @param __level The log level.
324 * @param __pathy The output path.
325 * @since 2024/03/15
327 public static void dumpLog(Logger __logger, LogLevel __level, Path __pathy)
331 for (String ln : Files.readAllLines(__pathy))
332 __logger.log(__level, ln);
334 catch (Throwable e)
336 e.printStackTrace();
341 * Loads the CMake cache from the given build.
343 * @param __logDir The build directory to use.
344 * @return The CMake cache.
345 * @throws IOException If it could not be read.
346 * @throws NullPointerException On null arguments.
347 * @since 2024/04/01
349 public static Map<String, String> loadCache(Path __logDir)
350 throws IOException, NullPointerException
352 if (__logDir == null)
353 throw new NullPointerException("NARG");
355 // Load in lines accordingly
356 Map<String, String> result = new LinkedHashMap<>();
357 for (String line : Files.readAllLines(
358 __logDir.resolve("CMakeCache.txt")))
360 // Comment?
361 if (line.startsWith("//") || line.startsWith("#"))
362 continue;
364 // Find equal sign between key and value
365 int eq = line.indexOf('=');
366 if (eq < 0)
367 continue;
369 // Split in
370 result.put(line.substring(0, eq).trim(),
371 line.substring(eq + 1).trim());
374 // Return parsed properties
375 return result;