1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
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
.util
;
12 import java
.io
.BufferedReader
;
13 import java
.io
.ByteArrayInputStream
;
14 import java
.io
.ByteArrayOutputStream
;
15 import java
.io
.IOException
;
16 import java
.io
.InputStream
;
17 import java
.io
.InputStreamReader
;
18 import java
.nio
.charset
.StandardCharsets
;
19 import java
.nio
.file
.Files
;
20 import java
.nio
.file
.Path
;
21 import java
.nio
.file
.Paths
;
22 import java
.nio
.file
.StandardOpenOption
;
23 import java
.util
.ArrayList
;
24 import java
.util
.Arrays
;
25 import java
.util
.Collection
;
26 import java
.util
.List
;
28 import java
.util
.regex
.Pattern
;
29 import org
.gradle
.internal
.os
.OperatingSystem
;
32 * This class provides support for the Fossil executable, to do some tasks
33 * and otherwise from within Gradle.
37 public final class FossilExe
39 /** Cached executable. */
40 @SuppressWarnings({"StaticVariableMayNotBeInitialized", "unused"})
41 private static FossilExe _cached
;
43 /** The executable path. */
44 private final Path exe
;
47 * Initializes the executable reference with the given path.
49 * @param __exe The executable path.
50 * @throws NullPointerException On null arguments.
53 public FossilExe(Path __exe
)
54 throws NullPointerException
57 throw new NullPointerException("NARG");
63 * Cats the given file name.
65 * @param __fileName The file name.
66 * @return The file data.
67 * @throws NullPointerException On null arguments.
70 public final byte[] cat(String __fileName
)
71 throws NullPointerException
73 if (__fileName
== null)
74 throw new NullPointerException("NARG");
76 return this.runRawOutput("cat", __fileName
);
80 * Returns the current fossil user.
82 * @return The current fossil user.
83 * @throws InvalidFossilExeException If the user is not valid.
86 public final String
currentUser()
87 throws InvalidFossilExeException
89 // Use first line found for the user
90 for (String line
: this.runLineOutput("user", "default"))
95 throw new InvalidFossilExeException("No default user set in Fossil, " +
96 "please set `fossil user default user.name`");
100 * Returns the executable path.
102 * @return The executable path.
105 public final Path
exePath()
111 * Runs the specified command and returns the process for it.
113 * @param __args Arguments to the command.
114 * @return The process of the command.
117 @SuppressWarnings("UseOfProcessBuilder")
118 public final Process
runCommand(String
... __args
)
120 ProcessBuilder builder
= new ProcessBuilder();
122 // The first argument is always the command
123 List
<String
> command
= new ArrayList
<>();
124 command
.add(this.exe
.toAbsolutePath().toString());
126 // Add all subsequent arguments
128 command
.addAll(Arrays
.<String
>asList(__args
));
131 builder
.command(command
);
133 // Standard output is always printed to the console, for debugging
134 builder
.redirectError(ProcessBuilder
.Redirect
.INHERIT
);
136 // Force specific locales and otherwise
137 Map
<String
, String
> env
= builder
.environment();
138 env
.put("LC_ALL", "C");
140 // Start the command but wrap IOException as it is annoying
143 return builder
.start();
145 catch (IOException e
)
147 throw new RuntimeException("Could not execute command.", e
);
152 * Executes the command and returns the lines used.
154 * @param __args The arguments to call.
155 * @return The lines used in the command.
158 public final Collection
<String
> runLineOutput(String
... __args
)
160 // Get the raw command bytes
161 Collection
<String
> rv
= new ArrayList
<>();
162 try (BufferedReader in
= new BufferedReader(new InputStreamReader(
163 new ByteArrayInputStream(this.runRawOutput(__args
)),
164 StandardCharsets
.UTF_8
)))
168 String ln
= in
.readLine();
177 // Could no read the command result
178 catch (IOException e
)
180 throw new RuntimeException("Line read/write error.", e
);
187 * Runs the command and returns the raw output.
189 * @param __args The commands to run.
190 * @return The raw output of the command.
193 public final byte[] runRawOutput(String
... __args
)
195 // Start the Fossil process
196 Process process
= this.runCommand(__args
);
198 // Read in the command data
199 try (InputStream in
= process
.getInputStream();
200 ByteArrayOutputStream out
= new ByteArrayOutputStream())
203 byte[] buf
= new byte[4096];
206 int rc
= in
.read(buf
);
211 out
.write(buf
, 0, rc
);
214 // Wait for it to complete
218 int exitCode
= process
.waitFor();
220 throw new RuntimeException(String
.format(
221 "Exited %s with failure %d: %d bytes",
222 Arrays
.asList(__args
), exitCode
,
223 out
.toByteArray().length
));
226 catch (InterruptedException ignored
)
231 return out
.toByteArray();
234 // Could not read the command result
235 catch (IOException e
)
237 throw new RuntimeException("Raw read/write error.", e
);
240 // Make sure the process stops
243 // Make sure the process is destroyed
249 * Gets the content of the specified file.
251 * @param __fileName The file name.
252 * @return The stream for the file data or {@code null} if no such file
253 * exists or it has no content.
254 * @throws NullPointerException On null arguments.
257 public final InputStream
unversionCat(String __fileName
)
258 throws NullPointerException
260 if (__fileName
== null)
261 throw new NullPointerException("NARG");
263 byte[] data
= this.unversionCatBytes(__fileName
);
267 return new ByteArrayInputStream(data
);
271 * Gets the content of the specified file.
273 * @param __fileName The file name.
274 * @return The stream for the file data or {@code null} if no such file
275 * exists or it has no content.
276 * @throws NullPointerException On null arguments.
279 public final byte[] unversionCatBytes(String __fileName
)
280 throws NullPointerException
282 // If no data is available, return nothing
283 byte[] data
= this.runRawOutput("unversion", "cat", __fileName
);
284 if (data
.length
== 0)
291 * Deletes the specified file.
293 * @param __path The path to delete.
294 * @throws NullPointerException On null arguments.
297 public void unversionDelete(String __path
)
298 throws NullPointerException
301 throw new NullPointerException("NARG");
303 this.runLineOutput("unversion", "rm", __path
);
307 * Returns the list of unversioned files.
309 * @return The list of unversioned files.
312 public final Collection
<String
> unversionList()
314 return this.runLineOutput("unversion", "ls");
318 * Stores unversion bytes.
320 * @param __fileName The file name.
321 * @param __data The data to store.
322 * @throws NullPointerException On null arguments.
325 public final void unversionStoreBytes(String __fileName
, byte[] __data
)
326 throws NullPointerException
328 if (__fileName
== null || __data
== null)
329 throw new NullPointerException("NARG");
331 // Fossil accepts files as unversioned input
332 Path tempFile
= null;
335 // Setup temporary file
336 tempFile
= Files
.createTempFile("squirreljme-uvfile",
339 // Write data to file
340 Files
.write(tempFile
, __data
, StandardOpenOption
.WRITE
,
341 StandardOpenOption
.TRUNCATE_EXISTING
,
342 StandardOpenOption
.CREATE
);
344 // Store the file data
345 this.runRawOutput("unversion", "add",
346 tempFile
.toAbsolutePath().toString(),
350 // Could not write data
351 catch (IOException e
)
353 throw new RuntimeException(
354 "Could not store file: " + __fileName
, e
);
357 // Clean out file, if it exists
360 if (tempFile
!= null)
363 Files
.delete(tempFile
);
365 catch (IOException ignored
)
372 * Stores unversion bytes.
374 * @param __fileName The file name.
375 * @param __data The data to store.
376 * @throws IOException On read errors.
377 * @throws NullPointerException On null arguments.
380 public final void unversionStoreBytes(String __fileName
,
382 throws IOException
, NullPointerException
384 if (__fileName
== null || __data
== null)
385 throw new NullPointerException("NARG");
388 try (ByteArrayOutputStream baos
= new ByteArrayOutputStream())
391 byte[] buf
= new byte[16384];
394 int rc
= __data
.read(buf
);
400 baos
.write(buf
, 0, rc
);
404 bytes
= baos
.toByteArray();
407 // Forward to store call
408 this.unversionStoreBytes(__fileName
, bytes
);
412 * Returns the fossil version.
414 * @return The fossil version.
417 public final FossilVersion
version()
419 Collection
<String
> lines
= this.runLineOutput("version");
421 // Try to find the fossil version
422 for (String line
: lines
)
424 // Ignore lines not containing this text
425 if (!line
.toLowerCase().contains("this is fossil version"))
428 // Try to find the version number
429 for (String split
: line
.split("[ \t]"))
431 // Ignore blank sequences
435 // Use the split that starts with a number
436 char first
= split
.charAt(0);
437 if (first
>= '1' && first
<= '9')
440 return new FossilVersion(split
);
442 catch (IllegalArgumentException e
)
444 throw new InvalidFossilExeException("Invalid Exe", e
);
449 // This is not a good thing
450 throw new InvalidFossilExeException("Could not find fossil version.");
454 * Attempts to locate the fossil executable.
456 * @return The executable instance.
457 * @throws InvalidFossilExeException If an executable could not be found.
460 @SuppressWarnings({"CallToSystemGetenv",
461 "StaticVariableUsedBeforeInitialization"})
462 public static FossilExe
instance()
463 throws InvalidFossilExeException
465 // Pre-cached already?
466 FossilExe rv
= FossilExe
._cached
;
471 Path maybe
= PathUtils
.findPathExecutable("fossil");
473 throw new InvalidFossilExeException(
474 "Could not find Fossil executable.");
477 rv
= new FossilExe(maybe
);
478 FossilExe
._cached
= rv
;
483 * Check to see if Fossil is available.
485 * @param __withUser Also check that the user is set?
486 * @return If Fossil is available.
489 public static boolean isAvailable(boolean __withUser
)
493 FossilExe exe
= FossilExe
.instance();
495 // These will throw exceptions if not valid
498 // Check user as well?
506 catch (InvalidFossilExeException ignored
)