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 (OperatingSystem
.current() == OperatingSystem
.WINDOWS
)
72 String programFiles
= System
.getenv("PROGRAMFILES");
73 if (programFiles
!= null)
75 Path maybe
= Paths
.get(programFiles
).resolve("CMake")
76 .resolve("bin").resolve("cmake.exe");
77 if (Files
.exists(maybe
))
86 * Requests the version of CMake.
88 * @return The resultant CMake version or {@code null} if there is no
92 public static VersionNumber
cmakeExeVersion()
94 // We need the CMake executable
95 Path cmakeExe
= CMakeUtils
.cmakeExePath();
101 String rawStr
= CMakeUtils
.cmakeExecuteOutput("version",
104 // Read in what looks like a version number
105 try (BufferedReader buf
= new BufferedReader(
106 new StringReader(rawStr
)))
110 String ln
= buf
.readLine();
114 // Remove any whitespace and make it lowercase so it is
116 ln
= ln
.trim().toLowerCase(Locale
.ROOT
);
118 // Is this the version string?
119 if (ln
.startsWith("cmake version"))
120 return VersionNumber
.parse(
121 ln
.substring("cmake version".length()).trim());
126 throw new RuntimeException(
127 "CMake executed but there was no version.");
129 catch (IOException __e
)
131 throw new RuntimeException("Could not determine CMake version.",
137 * Executes a CMake task.
139 * @param __logger The logger to use.
140 * @param __logName The name of the log.
141 * @param __cmakeBuild The CMake build directory.
142 * @param __args CMake arguments.
143 * @return The CMake exit value.
144 * @throws IOException On read/write or execution errors.
147 public static int cmakeExecute(Logger __logger
, String __logName
,
148 Path __cmakeBuild
, String
... __args
)
151 if (__cmakeBuild
== null)
152 throw new NullPointerException("NARG");
155 Path outLog
= __cmakeBuild
.resolve(__logName
+ ".out");
156 Path errLog
= __cmakeBuild
.resolve(__logName
+ ".err");
158 // Make sure directories exist first
159 Files
.createDirectories(outLog
.getParent());
160 Files
.createDirectories(errLog
.getParent());
162 // Run with log wrapping
163 try (OutputStream stdOut
= Files
.newOutputStream(outLog
,
164 StandardOpenOption
.CREATE
, StandardOpenOption
.WRITE
,
165 StandardOpenOption
.TRUNCATE_EXISTING
);
166 OutputStream stdErr
= Files
.newOutputStream(errLog
,
167 StandardOpenOption
.CREATE
, StandardOpenOption
.WRITE
,
168 StandardOpenOption
.TRUNCATE_EXISTING
))
171 return CMakeUtils
.cmakeExecutePipe(null, stdOut
, stdErr
,
175 // Dump logs to Gradle
178 CMakeUtils
.dumpLog(__logger
,
179 LogLevel
.LIFECYCLE
, outLog
);
180 CMakeUtils
.dumpLog(__logger
,
181 LogLevel
.ERROR
, errLog
);
186 * Executes a CMake task and returns the output as a string.
188 * @param __buildType The build type used.
189 * @param __args CMake arguments.
190 * @throws IOException On read/write or execution errors.
193 public static String
cmakeExecuteOutput(String __buildType
,
197 try (ByteArrayOutputStream baos
= new ByteArrayOutputStream())
200 CMakeUtils
.cmakeExecutePipe(null, baos
, null,
201 __buildType
, __args
);
204 return baos
.toString("utf-8");
209 * Executes a CMake task to the given pipe.
211 * @param __in The standard input for the task.
212 * @param __out The standard output for the task.
213 * @param __err The standard error for the task.
214 * @param __buildType The build type used.
215 * @param __args CMake arguments.
216 * @return The CMake exit value.
217 * @throws IOException On read/write or execution errors.
220 public static int cmakeExecutePipe(InputStream __in
,
221 OutputStream __out
, OutputStream __err
, String __buildType
,
225 return CMakeUtils
.cmakeExecutePipe(true, __in
, __out
, __err
,
226 __buildType
, __args
);
230 * Executes a CMake task to the given pipe.
232 * @param __fail Emit failure if exit status is non-zero.
233 * @param __in The standard input for the task.
234 * @param __out The standard output for the task.
235 * @param __err The standard error for the task.
236 * @param __buildType The build type used.
237 * @param __args CMake arguments.
238 * @return The CMake exit value.
239 * @throws IOException On read/write or execution errors.
242 public static int cmakeExecutePipe(boolean __fail
, InputStream __in
,
243 OutputStream __out
, OutputStream __err
, String __buildType
,
248 Path cmakePath
= CMakeUtils
.cmakeExePath();
249 if (cmakePath
== null)
250 throw new RuntimeException("CMake not found.");
252 // Determine run arguments
253 List
<String
> args
= new ArrayList
<>();
254 args
.add(cmakePath
.toAbsolutePath().toString());
255 if (__args
!= null && __args
.length
> 0)
256 args
.addAll(Arrays
.asList(__args
));
258 // Set executable process
259 ProcessBuilder procBuilder
= new ProcessBuilder();
260 procBuilder
.command(args
);
262 // Log the output somewhere
264 procBuilder
.redirectInput(ProcessBuilder
.Redirect
.PIPE
);
266 procBuilder
.redirectOutput(ProcessBuilder
.Redirect
.PIPE
);
268 procBuilder
.redirectError(ProcessBuilder
.Redirect
.PIPE
);
271 Process proc
= procBuilder
.start();
273 // Wait for it to complete
274 try (ForwardStream fwdOut
= (__out
== null ?
null :
275 new ForwardInputToOutput(proc
.getInputStream(), __out
));
276 ForwardStream fwdErr
= (__err
== null ?
null :
277 new ForwardInputToOutput(proc
.getErrorStream(), __err
)))
281 fwdOut
.runThread("cmake-stdout");
285 fwdErr
.runThread("cmake-stderr");
287 // Wait for completion, stop if it takes too long
288 if (!proc
.waitFor(15, TimeUnit
.MINUTES
) ||
289 (__fail
&& proc
.exitValue() != 0))
290 throw new RuntimeException(String
.format(
291 "CMake failed to %s...", __buildType
));
293 // Use the given exit value
294 return proc
.exitValue();
296 catch (InterruptedException __e
)
298 throw new RuntimeException("CMake timed out!", __e
);
308 * Dumps log to the output.
310 * @param __logger The logger to output to.
311 * @param __level The log level.
312 * @param __pathy The output path.
315 public static void dumpLog(Logger __logger
, LogLevel __level
, Path __pathy
)
319 for (String ln
: Files
.readAllLines(__pathy
))
320 __logger
.log(__level
, ln
);
329 * Loads the CMake cache from the given build.
331 * @param __cmakeBuild The build directory to use.
332 * @return The CMake cache.
333 * @throws IOException If it could not be read.
334 * @throws NullPointerException On null arguments.
337 public static Map
<String
, String
> loadCache(Path __cmakeBuild
)
338 throws IOException
, NullPointerException
340 if (__cmakeBuild
== null)
341 throw new NullPointerException("NARG");
343 // Load in lines accordingly
344 Map
<String
, String
> result
= new LinkedHashMap
<>();
345 for (String line
: Files
.readAllLines(
346 __cmakeBuild
.resolve("CMakeCache.txt")))
349 if (line
.startsWith("//") || line
.startsWith("#"))
352 // Find equal sign between key and value
353 int eq
= line
.indexOf('=');
358 result
.put(line
.substring(0, eq
).trim(),
359 line
.substring(eq
+ 1).trim());
362 // Return parsed properties