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
;
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
;
39 * Utilities for CMake.
43 public final class CMakeUtils
55 * Returns the path where CMake is available.
57 * @return The CMake path.
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
))
84 if (cmakePath
== null &&
85 OperatingSystem
.current() == OperatingSystem
.MAC_OS
)
87 Path maybe
= Paths
.get("/").resolve("opt")
88 .resolve("homebrew").resolve("bin")
90 if (Files
.exists(maybe
))
98 * Requests the version of CMake.
100 * @return The resultant CMake version or {@code null} if there is no
104 public static VersionNumber
cmakeExeVersion()
106 // We need the CMake executable
107 Path cmakeExe
= CMakeUtils
.cmakeExePath();
108 if (cmakeExe
== null)
113 String rawStr
= CMakeUtils
.cmakeExecuteOutput("version",
116 // Read in what looks like a version number
117 try (BufferedReader buf
= new BufferedReader(
118 new StringReader(rawStr
)))
122 String ln
= buf
.readLine();
126 // Remove any whitespace and make it lowercase so it is
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());
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.",
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.
159 public static int cmakeExecute(Logger __logger
, String __logName
,
160 Path __logDir
, String
... __args
)
163 if (__logDir
== null)
164 throw new NullPointerException("NARG");
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
))
183 return CMakeUtils
.cmakeExecutePipe(null, stdOut
, stdErr
,
187 // Dump logs to Gradle
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.
205 public static String
cmakeExecuteOutput(String __buildType
,
209 try (ByteArrayOutputStream baos
= new ByteArrayOutputStream())
212 CMakeUtils
.cmakeExecutePipe(null, baos
, null,
213 __buildType
, __args
);
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.
232 public static int cmakeExecutePipe(InputStream __in
,
233 OutputStream __out
, OutputStream __err
, String __buildType
,
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.
254 public static int cmakeExecutePipe(boolean __fail
, InputStream __in
,
255 OutputStream __out
, OutputStream __err
, String __buildType
,
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
276 procBuilder
.redirectInput(ProcessBuilder
.Redirect
.PIPE
);
278 procBuilder
.redirectOutput(ProcessBuilder
.Redirect
.PIPE
);
280 procBuilder
.redirectError(ProcessBuilder
.Redirect
.PIPE
);
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
)))
293 fwdOut
.runThread("cmake-stdout");
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
);
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.
327 public static void dumpLog(Logger __logger
, LogLevel __level
, Path __pathy
)
331 for (String ln
: Files
.readAllLines(__pathy
))
332 __logger
.log(__level
, ln
);
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.
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")))
361 if (line
.startsWith("//") || line
.startsWith("#"))
364 // Find equal sign between key and value
365 int eq
= line
.indexOf('=');
370 result
.put(line
.substring(0, eq
).trim(),
371 line
.substring(eq
+ 1).trim());
374 // Return parsed properties