1 package org
.argeo
.build
;
3 import static java
.lang
.System
.Logger
.Level
.DEBUG
;
4 import static java
.lang
.System
.Logger
.Level
.ERROR
;
5 import static java
.lang
.System
.Logger
.Level
.INFO
;
6 import static java
.lang
.System
.Logger
.Level
.WARNING
;
9 import java
.io
.IOException
;
10 import java
.io
.InputStream
;
11 import java
.io
.OutputStream
;
12 import java
.io
.PrintWriter
;
13 import java
.lang
.System
.Logger
;
14 import java
.lang
.System
.Logger
.Level
;
15 import java
.lang
.management
.ManagementFactory
;
16 import java
.nio
.file
.DirectoryStream
;
17 import java
.nio
.file
.FileVisitResult
;
18 import java
.nio
.file
.Files
;
19 import java
.nio
.file
.Path
;
20 import java
.nio
.file
.PathMatcher
;
21 import java
.nio
.file
.Paths
;
22 import java
.nio
.file
.SimpleFileVisitor
;
23 import java
.nio
.file
.attribute
.BasicFileAttributes
;
24 import java
.util
.ArrayList
;
25 import java
.util
.Arrays
;
26 import java
.util
.HashMap
;
27 import java
.util
.Iterator
;
28 import java
.util
.List
;
30 import java
.util
.Objects
;
31 import java
.util
.Properties
;
32 import java
.util
.StringJoiner
;
33 import java
.util
.StringTokenizer
;
34 import java
.util
.TreeMap
;
35 import java
.util
.concurrent
.CompletableFuture
;
36 import java
.util
.jar
.Attributes
;
37 import java
.util
.jar
.JarEntry
;
38 import java
.util
.jar
.JarOutputStream
;
39 import java
.util
.jar
.Manifest
;
40 import java
.util
.zip
.Deflater
;
42 import org
.eclipse
.jdt
.core
.compiler
.CompilationProgress
;
44 import aQute
.bnd
.osgi
.Analyzer
;
45 import aQute
.bnd
.osgi
.Jar
;
48 * Minimalistic OSGi compiler and packager, meant to be used as a single file
49 * without being itself compiled first. It depends on the Eclipse batch compiler
50 * (aka. ECJ) and the BND Libs library for OSGi metadata generation (which
51 * itselfs depends on slf4j).<br/>
53 * For example, a typical system call would be:<br/>
54 * <code>java -cp "/path/to/ECJ jar:/path/to/bndlib jar:/path/to/SLF4J jar" /path/to/cloned/argeo-build/src/org/argeo/build/Make.java action --option1 argument1 argument2 --option2 argument3 </code>
57 private final static Logger logger
= System
.getLogger(Make
.class.getName());
60 * Environment variable on whether sources should be packaged separately or
61 * integrated in the bundles.
63 private final static String ENV_SOURCE_BUNDLES
= "SOURCE_BUNDLES";
66 * Environment variable on whether legal files at the root of the sources should
67 * be included in the generated bundles. Should be set to true when building
68 * third-party software in order no to include the build harness license into
69 * the generated bundles.
71 private final static String ENV_NO_SDK_LEGAL
= "NO_SDK_LEGAL";
74 * Environment variable to override the default location for the Argeo Build
75 * configuration files. Typically used if Argeo Build has been compiled and
76 * packaged separately.
78 private final static String ENV_ARGEO_BUILD_CONFIG
= "ARGEO_BUILD_CONFIG";
80 /** Name of the local-specific Makefile (sdk.mk). */
81 final static String SDK_MK
= "sdk.mk";
82 /** Name of the branch definition Makefile (branch.mk). */
83 final static String BRANCH_MK
= "branch.mk";
85 /** The execution directory (${user.dir}). */
86 final Path execDirectory
;
87 /** Base of the source code, typically the cloned git repository. */
88 final Path sdkSrcBase
;
90 * The base of the builder, typically a submodule pointing to the INCLUDEpublic
91 * argeo-build directory.
93 final Path argeoBuildBase
;
94 /** The base of the build for all layers. */
95 final Path sdkBuildBase
;
96 /** The base of the build for this layer. */
98 /** The base of the a2 output for all layers. */
100 /** The base of the a2 sources when packages separately. */
101 final Path a2srcOutput
;
103 /** Whether sources should be packaged separately. */
104 final boolean sourceBundles
;
105 /** Whether common legal files should be included. */
106 final boolean noSdkLegal
;
108 /** Constructor initialises the base directories. */
109 public Make() throws IOException
{
110 sourceBundles
= Boolean
.parseBoolean(System
.getenv(ENV_SOURCE_BUNDLES
));
112 logger
.log(Level
.INFO
, "Sources will be packaged separately");
113 noSdkLegal
= Boolean
.parseBoolean(System
.getenv(ENV_NO_SDK_LEGAL
));
115 logger
.log(Level
.INFO
, "SDK legal files will NOT be included");
117 execDirectory
= Paths
.get(System
.getProperty("user.dir"));
118 Path sdkMkP
= findSdkMk(execDirectory
);
119 Objects
.requireNonNull(sdkMkP
, "No " + SDK_MK
+ " found under " + execDirectory
);
121 Map
<String
, String
> context
= readeMakefileVariables(sdkMkP
);
122 sdkSrcBase
= Paths
.get(context
.computeIfAbsent("SDK_SRC_BASE", (key
) -> {
123 throw new IllegalStateException(key
+ " not found");
124 })).toAbsolutePath();
126 Path argeoBuildBaseT
= sdkSrcBase
.resolve("sdk/argeo-build");
127 if (!Files
.exists(argeoBuildBaseT
)) {
128 String fromEnv
= System
.getenv(ENV_ARGEO_BUILD_CONFIG
);
130 argeoBuildBaseT
= Paths
.get(fromEnv
);
131 if (fromEnv
== null || !Files
.exists(argeoBuildBaseT
)) {
132 throw new IllegalStateException(
133 "Argeo Build not found. Did you initialise the git submodules or set the "
134 + ENV_ARGEO_BUILD_CONFIG
+ " environment variable?");
137 argeoBuildBase
= argeoBuildBaseT
;
139 sdkBuildBase
= Paths
.get(context
.computeIfAbsent("SDK_BUILD_BASE", (key
) -> {
140 throw new IllegalStateException(key
+ " not found");
141 })).toAbsolutePath();
142 buildBase
= sdkBuildBase
.resolve(sdkSrcBase
.getFileName());
143 a2Output
= sdkBuildBase
.resolve("a2");
144 a2srcOutput
= sdkBuildBase
.resolve("a2.src");
150 /** Compile and create the bundles in one go. */
151 void all(Map
<String
, List
<String
>> options
) throws IOException
{
156 /** Compile all the bundles which have been passed via the --bundle argument. */
157 void compile(Map
<String
, List
<String
>> options
) throws IOException
{
158 List
<String
> bundles
= options
.get("--bundles");
159 Objects
.requireNonNull(bundles
, "--bundles argument must be set");
160 if (bundles
.isEmpty())
163 List
<String
> a2Categories
= options
.getOrDefault("--dep-categories", new ArrayList
<>());
164 List
<String
> a2Bases
= options
.getOrDefault("--a2-bases", new ArrayList
<>());
165 if (a2Bases
.isEmpty() || !a2Bases
.contains(a2Output
.toString())) {
166 a2Bases
.add(a2Output
.toString());
169 List
<String
> compilerArgs
= new ArrayList
<>();
171 Path ecjArgs
= argeoBuildBase
.resolve("ecj.args");
172 compilerArgs
.add("@" + ecjArgs
);
175 if (!a2Categories
.isEmpty()) {
176 // We will keep only the highest major.minor
177 // and order by bundle name, for predictability
178 Map
<String
, A2Jar
> a2Jars
= new TreeMap
<>();
180 // StringJoiner modulePath = new StringJoiner(File.pathSeparator);
181 for (String a2Base
: a2Bases
) {
182 categories
: for (String a2Category
: a2Categories
) {
183 Path a2Dir
= Paths
.get(a2Base
).resolve(a2Category
);
184 if (!Files
.exists(a2Dir
))
186 // modulePath.add(a2Dir.toString());
187 for (Path jarP
: Files
.newDirectoryStream(a2Dir
, (p
) -> p
.getFileName().toString().endsWith(".jar")
188 && !p
.getFileName().toString().endsWith(".src.jar"))) {
189 A2Jar a2Jar
= new A2Jar(jarP
);
190 if (a2Jars
.containsKey(a2Jar
.name
)) {
191 A2Jar current
= a2Jars
.get(a2Jar
.name
);
192 if (a2Jar
.major
> current
.major
)
193 a2Jars
.put(a2Jar
.name
, a2Jar
);
194 else if (a2Jar
.major
== current
.major
//
195 // if minor equals, we take the last one
196 && a2Jar
.minor
>= current
.minor
)
197 a2Jars
.put(a2Jar
.name
, a2Jar
);
199 a2Jars
.put(a2Jar
.name
, a2Jar
);
205 StringJoiner classPath
= new StringJoiner(File
.pathSeparator
);
206 for (Iterator
<A2Jar
> it
= a2Jars
.values().iterator(); it
.hasNext();)
207 classPath
.add(it
.next().path
.toString());
209 compilerArgs
.add("-cp");
210 compilerArgs
.add(classPath
.toString());
211 // compilerArgs.add("--module-path");
212 // compilerArgs.add(modulePath.toString());
216 for (String bundle
: bundles
) {
217 StringBuilder sb
= new StringBuilder();
218 Path bundlePath
= execDirectory
.resolve(bundle
);
219 if (!Files
.exists(bundlePath
)) {
220 if (bundles
.size() == 1) {
221 logger
.log(WARNING
, "Bundle " + bundle
+ " not found in " + execDirectory
222 + ", assuming this is this directory, as only one bundle was requested.");
223 bundlePath
= execDirectory
;
225 throw new IllegalArgumentException("Bundle " + bundle
+ " not found in " + execDirectory
);
227 sb
.append(bundlePath
.resolve("src"));
229 compilerArgs
.add(sb
.toString());
230 sb
= new StringBuilder();
231 sb
.append(buildBase
.resolve(bundle
).resolve("bin"));
233 compilerArgs
.add(sb
.toString());
236 if (logger
.isLoggable(INFO
))
237 compilerArgs
.add("-time");
239 if (logger
.isLoggable(DEBUG
)) {
240 logger
.log(DEBUG
, "Compiler arguments:");
241 for (String arg
: compilerArgs
)
242 logger
.log(DEBUG
, arg
);
245 boolean success
= org
.eclipse
.jdt
.core
.compiler
.batch
.BatchCompiler
.compile(
246 compilerArgs
.toArray(new String
[compilerArgs
.size()]), new PrintWriter(System
.out
),
247 new PrintWriter(System
.err
), new MakeCompilationProgress());
248 if (!success
) // kill the process if compilation failed
249 throw new IllegalStateException("Compilation failed");
252 /** Package the bundles. */
253 void bundle(Map
<String
, List
<String
>> options
) throws IOException
{
255 List
<String
> bundles
= options
.get("--bundles");
256 Objects
.requireNonNull(bundles
, "--bundles argument must be set");
257 if (bundles
.isEmpty())
260 List
<String
> categories
= options
.get("--category");
261 Objects
.requireNonNull(bundles
, "--category argument must be set");
262 if (categories
.size() != 1)
263 throw new IllegalArgumentException("One and only one --category must be specified");
264 String category
= categories
.get(0);
267 Path branchMk
= sdkSrcBase
.resolve(BRANCH_MK
);
268 if (Files
.exists(branchMk
)) {
269 Map
<String
, String
> branchVariables
= readeMakefileVariables(branchMk
);
270 branch
= branchVariables
.get("BRANCH");
275 long begin
= System
.currentTimeMillis();
276 // create jars in parallel
277 List
<CompletableFuture
<Void
>> toDos
= new ArrayList
<>();
278 for (String bundle
: bundles
) {
279 toDos
.add(CompletableFuture
.runAsync(() -> {
281 createBundle(branch
, bundle
, category
);
282 } catch (IOException e
) {
283 throw new RuntimeException("Packaging of " + bundle
+ " failed", e
);
287 CompletableFuture
.allOf(toDos
.toArray(new CompletableFuture
[toDos
.size()])).join();
288 long duration
= System
.currentTimeMillis() - begin
;
289 logger
.log(INFO
, "Packaging took " + duration
+ " ms");
292 /** Package a single bundle. */
293 void createBundle(String branch
, String bundle
, String category
) throws IOException
{
295 if (!Files
.exists(execDirectory
.resolve(bundle
))) {
297 "Bundle " + bundle
+ " not found in " + execDirectory
+ ", assuming this is this directory.");
298 source
= execDirectory
;
300 source
= execDirectory
.resolve(bundle
);
302 Path srcP
= source
.resolve("src");
304 Path compiled
= buildBase
.resolve(bundle
);
305 String bundleSymbolicName
= source
.getFileName().toString();
308 Properties properties
= new Properties();
309 Path argeoBnd
= argeoBuildBase
.resolve("argeo.bnd");
310 try (InputStream in
= Files
.newInputStream(argeoBnd
)) {
314 if (branch
!= null) {
315 Path branchBnd
= sdkSrcBase
.resolve("sdk/branches/" + branch
+ ".bnd");
316 if (Files
.exists(branchBnd
))
317 try (InputStream in
= Files
.newInputStream(branchBnd
)) {
322 Path bndBnd
= source
.resolve("bnd.bnd");
323 if (Files
.exists(bndBnd
))
324 try (InputStream in
= Files
.newInputStream(bndBnd
)) {
329 if (!properties
.containsKey("Bundle-SymbolicName"))
330 properties
.put("Bundle-SymbolicName", bundleSymbolicName
);
332 // Calculate MANIFEST
333 Path binP
= compiled
.resolve("bin");
334 if (!Files
.exists(binP
))
335 Files
.createDirectories(binP
);
337 try (Analyzer bndAnalyzer
= new Analyzer()) {
338 bndAnalyzer
.setProperties(properties
);
339 Jar jar
= new Jar(bundleSymbolicName
, binP
.toFile());
340 bndAnalyzer
.setJar(jar
);
341 manifest
= bndAnalyzer
.calcManifest();
342 } catch (Exception e
) {
343 throw new RuntimeException("Bnd analysis of " + compiled
+ " failed", e
);
346 String major
= properties
.getProperty("major");
347 Objects
.requireNonNull(major
, "'major' must be set");
348 String minor
= properties
.getProperty("minor");
349 Objects
.requireNonNull(minor
, "'minor' must be set");
352 Path manifestP
= compiled
.resolve("META-INF/MANIFEST.MF");
353 Files
.createDirectories(manifestP
.getParent());
354 try (OutputStream out
= Files
.newOutputStream(manifestP
)) {
359 List
<PathMatcher
> excludes
= new ArrayList
<>();
360 Path excludesP
= argeoBuildBase
.resolve("excludes.txt");
361 for (String line
: Files
.readAllLines(excludesP
)) {
362 PathMatcher pathMatcher
= excludesP
.getFileSystem().getPathMatcher("glob:" + line
);
363 excludes
.add(pathMatcher
);
366 Path bundleParent
= Paths
.get(bundle
).getParent();
367 Path a2JarDirectory
= bundleParent
!= null ? a2Output
.resolve(bundleParent
).resolve(category
)
368 : a2Output
.resolve(category
);
369 Path jarP
= a2JarDirectory
.resolve(compiled
.getFileName() + "." + major
+ "." + minor
+ ".jar");
370 Files
.createDirectories(jarP
.getParent());
372 try (JarOutputStream jarOut
= new JarOutputStream(Files
.newOutputStream(jarP
), manifest
)) {
373 jarOut
.setLevel(Deflater
.DEFAULT_COMPRESSION
);
374 // add all classes first
375 Files
.walkFileTree(binP
, new SimpleFileVisitor
<Path
>() {
377 public FileVisitResult
visitFile(Path file
, BasicFileAttributes attrs
) throws IOException
{
378 jarOut
.putNextEntry(new JarEntry(binP
.relativize(file
).toString()));
379 Files
.copy(file
, jarOut
);
380 return FileVisitResult
.CONTINUE
;
385 Files
.walkFileTree(source
, new SimpleFileVisitor
<Path
>() {
387 public FileVisitResult
preVisitDirectory(Path dir
, BasicFileAttributes attrs
) throws IOException
{
388 Path relativeP
= source
.relativize(dir
);
389 for (PathMatcher exclude
: excludes
)
390 if (exclude
.matches(relativeP
))
391 return FileVisitResult
.SKIP_SUBTREE
;
393 return FileVisitResult
.CONTINUE
;
397 public FileVisitResult
visitFile(Path file
, BasicFileAttributes attrs
) throws IOException
{
398 Path relativeP
= source
.relativize(file
);
399 for (PathMatcher exclude
: excludes
)
400 if (exclude
.matches(relativeP
))
401 return FileVisitResult
.CONTINUE
;
402 JarEntry entry
= new JarEntry(relativeP
.toString());
403 jarOut
.putNextEntry(entry
);
404 Files
.copy(file
, jarOut
);
405 return FileVisitResult
.CONTINUE
;
409 // Add all resources from src/
410 Files
.walkFileTree(srcP
, new SimpleFileVisitor
<Path
>() {
412 public FileVisitResult
visitFile(Path file
, BasicFileAttributes attrs
) throws IOException
{
413 if (file
.getFileName().toString().endsWith(".java")
414 || file
.getFileName().toString().endsWith(".class"))
415 return FileVisitResult
.CONTINUE
;
416 jarOut
.putNextEntry(new JarEntry(srcP
.relativize(file
).toString()));
417 if (!Files
.isDirectory(file
))
418 Files
.copy(file
, jarOut
);
419 return FileVisitResult
.CONTINUE
;
423 // add legal notices and licenses
424 for (Path p
: listLegalFilesToInclude(source
).values()) {
425 jarOut
.putNextEntry(new JarEntry(p
.getFileName().toString()));
426 Files
.copy(p
, jarOut
);
430 // TODO add effective BND, Eclipse project file, etc., in order to be able to
432 if (!sourceBundles
) {
433 copySourcesToJar(srcP
, jarOut
, "OSGI-OPT/src/");
437 if (sourceBundles
) {// create separate sources jar
438 Path a2srcJarDirectory
= bundleParent
!= null ? a2srcOutput
.resolve(bundleParent
).resolve(category
)
439 : a2srcOutput
.resolve(category
);
440 Files
.createDirectories(a2srcJarDirectory
);
441 Path srcJarP
= a2srcJarDirectory
.resolve(compiled
.getFileName() + "." + major
+ "." + minor
+ ".src.jar");
442 Manifest srcManifest
= new Manifest();
443 srcManifest
.getMainAttributes().put(Attributes
.Name
.MANIFEST_VERSION
, "1.0");
444 srcManifest
.getMainAttributes().putValue("Bundle-SymbolicName", bundleSymbolicName
+ ".src");
445 srcManifest
.getMainAttributes().putValue("Bundle-Version",
446 manifest
.getMainAttributes().getValue("Bundle-Version").toString());
447 srcManifest
.getMainAttributes().putValue("Eclipse-SourceBundle",
448 bundleSymbolicName
+ ";version=\"" + manifest
.getMainAttributes().getValue("Bundle-Version"));
450 try (JarOutputStream srcJarOut
= new JarOutputStream(Files
.newOutputStream(srcJarP
), srcManifest
)) {
451 copySourcesToJar(srcP
, srcJarOut
, "");
452 // add legal notices and licenses
453 for (Path p
: listLegalFilesToInclude(source
).values()) {
454 srcJarOut
.putNextEntry(new JarEntry(p
.getFileName().toString()));
455 Files
.copy(p
, srcJarOut
);
461 /** List the relevant legal files to include, from the SDK source base. */
462 Map
<String
, Path
> listLegalFilesToInclude(Path bundleBase
) throws IOException
{
463 Map
<String
, Path
> toInclude
= new HashMap
<>();
465 DirectoryStream
<Path
> sdkSrcLegal
= Files
.newDirectoryStream(sdkSrcBase
, (p
) -> {
466 String fileName
= p
.getFileName().toString();
467 return switch (fileName
) {
471 case "COPYING.LESSER":
477 for (Path p
: sdkSrcLegal
)
478 toInclude
.put(p
.getFileName().toString(), p
);
480 for (Iterator
<Map
.Entry
<String
, Path
>> entries
= toInclude
.entrySet().iterator(); entries
.hasNext();) {
481 Map
.Entry
<String
, Path
> entry
= entries
.next();
482 Path inBundle
= bundleBase
.resolve(entry
.getValue().getFileName());
483 // remove file if it is also defined at bundle level
484 // since it has already been copied
486 if (Files
.exists(inBundle
))
495 /** Add sources to a jar file */
496 void copySourcesToJar(Path srcP
, JarOutputStream srcJarOut
, String prefix
) throws IOException
{
497 Files
.walkFileTree(srcP
, new SimpleFileVisitor
<Path
>() {
499 public FileVisitResult
visitFile(Path file
, BasicFileAttributes attrs
) throws IOException
{
500 srcJarOut
.putNextEntry(new JarEntry(prefix
+ srcP
.relativize(file
).toString()));
501 if (!Files
.isDirectory(file
))
502 Files
.copy(file
, srcJarOut
);
503 return FileVisitResult
.CONTINUE
;
509 * Recursively find the base source directory (which contains the
510 * <code>{@value #SDK_MK}</code> file).
512 Path
findSdkMk(Path directory
) {
513 Path sdkMkP
= directory
.resolve(SDK_MK
);
514 if (Files
.exists(sdkMkP
)) {
515 return sdkMkP
.toAbsolutePath();
517 if (directory
.getParent() == null)
519 return findSdkMk(directory
.getParent());
523 * Reads Makefile variable assignments of the form =, :=, or ?=, ignoring white
524 * spaces. To be used with very simple included Makefiles only.
526 Map
<String
, String
> readeMakefileVariables(Path path
) throws IOException
{
527 Map
<String
, String
> context
= new HashMap
<>();
528 List
<String
> sdkMkLines
= Files
.readAllLines(path
);
529 lines
: for (String line
: sdkMkLines
) {
530 StringTokenizer st
= new StringTokenizer(line
, " :=?");
531 if (!st
.hasMoreTokens())
533 String key
= st
.nextToken();
534 if (!st
.hasMoreTokens())
536 String value
= st
.nextToken();
537 if (st
.hasMoreTokens()) // probably not a simple variable assignment
539 context
.put(key
, value
);
544 /** Main entry point, interpreting actions and arguments. */
545 public static void main(String
... args
) {
546 if (args
.length
== 0)
547 throw new IllegalArgumentException("At least an action must be provided");
549 String action
= args
[actionIndex
];
550 if (args
.length
> actionIndex
+ 1 && !args
[actionIndex
+ 1].startsWith("-"))
551 throw new IllegalArgumentException(
552 "Action " + action
+ " must be followed by an option: " + Arrays
.asList(args
));
554 Map
<String
, List
<String
>> options
= new HashMap
<>();
555 String currentOption
= null;
556 for (int i
= actionIndex
+ 1; i
< args
.length
; i
++) {
557 if (args
[i
].startsWith("-")) {
558 currentOption
= args
[i
];
559 if (!options
.containsKey(currentOption
))
560 options
.put(currentOption
, new ArrayList
<>());
563 options
.get(currentOption
).add(args
[i
]);
568 Make argeoMake
= new Make();
570 case "compile" -> argeoMake
.compile(options
);
571 case "bundle" -> argeoMake
.bundle(options
);
572 case "all" -> argeoMake
.all(options
);
574 default -> throw new IllegalArgumentException("Unkown action: " + action
);
577 long jvmUptime
= ManagementFactory
.getRuntimeMXBean().getUptime();
578 logger
.log(INFO
, "Make.java action '" + action
+ "' succesfully completed after " + (jvmUptime
/ 1000) + "."
579 + (jvmUptime
% 1000) + " s");
580 } catch (Exception e
) {
581 long jvmUptime
= ManagementFactory
.getRuntimeMXBean().getUptime();
582 logger
.log(ERROR
, "Make.java action '" + action
+ "' failed after " + (jvmUptime
/ 1000) + "."
583 + (jvmUptime
% 1000) + " s", e
);
588 /** A jar file in A2 format */
598 String fileName
= path
.getFileName().toString();
599 fileName
= fileName
.substring(0, fileName
.lastIndexOf('.'));
600 minor
= Integer
.parseInt(fileName
.substring(fileName
.lastIndexOf('.') + 1));
601 fileName
= fileName
.substring(0, fileName
.lastIndexOf('.'));
602 major
= Integer
.parseInt(fileName
.substring(fileName
.lastIndexOf('.') + 1));
603 name
= fileName
.substring(0, fileName
.lastIndexOf('.'));
604 } catch (Exception e
) {
605 throw new IllegalArgumentException("Badly formatted A2 jar " + path
, e
);
611 * An ECJ {@link CompilationProgress} printing a progress bar while compiling.
613 static class MakeCompilationProgress
extends CompilationProgress
{
614 private int totalWork
;
615 private long currentChunk
= 0;
616 private long chunksCount
= 80;
619 public void worked(int workIncrement
, int remainingWork
) {
620 if (!logger
.isLoggable(Level
.INFO
)) // progress bar only at INFO level
622 long chunk
= ((totalWork
- remainingWork
) * chunksCount
) / totalWork
;
623 if (chunk
!= currentChunk
) {
624 currentChunk
= chunk
;
625 for (long i
= 0; i
< currentChunk
; i
++) {
626 System
.out
.print("#");
628 for (long i
= currentChunk
; i
< chunksCount
; i
++) {
629 System
.out
.print("-");
631 System
.out
.print("\r");
633 if (remainingWork
== 0)
634 System
.out
.print("\n");
638 public void setTaskName(String name
) {
642 public boolean isCanceled() {
651 public void begin(int remainingWork
) {
652 this.totalWork
= remainingWork
;