1 package org
.argeo
.build
;
4 import java
.io
.IOException
;
5 import java
.io
.InputStream
;
6 import java
.io
.OutputStream
;
7 import java
.io
.PrintWriter
;
8 import java
.lang
.management
.ManagementFactory
;
9 import java
.nio
.file
.FileVisitResult
;
10 import java
.nio
.file
.Files
;
11 import java
.nio
.file
.Path
;
12 import java
.nio
.file
.PathMatcher
;
13 import java
.nio
.file
.Paths
;
14 import java
.nio
.file
.SimpleFileVisitor
;
15 import java
.nio
.file
.attribute
.BasicFileAttributes
;
16 import java
.util
.ArrayList
;
17 import java
.util
.Arrays
;
18 import java
.util
.HashMap
;
19 import java
.util
.List
;
21 import java
.util
.Objects
;
22 import java
.util
.Properties
;
23 import java
.util
.StringJoiner
;
24 import java
.util
.StringTokenizer
;
25 import java
.util
.jar
.JarEntry
;
26 import java
.util
.jar
.JarOutputStream
;
27 import java
.util
.jar
.Manifest
;
29 import org
.eclipse
.jdt
.core
.compiler
.CompilationProgress
;
31 import aQute
.bnd
.osgi
.Analyzer
;
32 import aQute
.bnd
.osgi
.Jar
;
35 private final static String SDK_MK
= "sdk.mk";
37 final Path execDirectory
;
38 final Path sdkSrcBase
;
39 final Path argeoBuildBase
;
40 final Path sdkBuildBase
;
44 public Make() throws IOException
{
45 execDirectory
= Paths
.get(System
.getProperty("user.dir"));
46 Path sdkMkP
= findSdkMk(execDirectory
);
47 Objects
.requireNonNull(sdkMkP
, "No " + SDK_MK
+ " found under " + execDirectory
);
49 Map
<String
, String
> context
= new HashMap
<>();
50 List
<String
> sdkMkLines
= Files
.readAllLines(sdkMkP
);
51 lines
: for (String line
: sdkMkLines
) {
52 StringTokenizer st
= new StringTokenizer(line
, " :=");
53 if (!st
.hasMoreTokens())
55 String key
= st
.nextToken();
56 if (!st
.hasMoreTokens())
58 String value
= st
.nextToken();
59 context
.put(key
, value
);
62 sdkSrcBase
= Paths
.get(context
.computeIfAbsent("SDK_SRC_BASE", (key
) -> {
63 throw new IllegalStateException(key
+ " not found");
65 argeoBuildBase
= sdkSrcBase
.resolve("sdk/argeo-build");
67 sdkBuildBase
= Paths
.get(context
.computeIfAbsent("SDK_BUILD_BASE", (key
) -> {
68 throw new IllegalStateException(key
+ " not found");
70 buildBase
= sdkBuildBase
.resolve(sdkSrcBase
.getFileName());
71 a2Output
= sdkBuildBase
.resolve("a2");
78 void all(Map
<String
, List
<String
>> options
) throws IOException
{
79 // List<String> a2Bundles = options.get("--a2-bundles");
80 // if (a2Bundles == null)
81 // throw new IllegalArgumentException("--a2-bundles must be specified");
82 // List<String> bundles = new ArrayList<>();
83 // for (String a2Bundle : a2Bundles) {
84 // Path a2BundleP = Paths.get(a2Bundle);
85 // Path bundleP = a2Output.relativize(a2BundleP.getParent().getParent().resolve(a2BundleP.getFileName()));
86 // bundles.add(bundleP.toString());
88 // options.put("--bundles", bundles);
93 @SuppressWarnings("restriction")
94 void compile(Map
<String
, List
<String
>> options
) throws IOException
{
95 List
<String
> bundles
= options
.get("--bundles");
96 Objects
.requireNonNull(bundles
, "--bundles argument must be set");
97 if (bundles
.isEmpty())
100 List
<String
> a2Categories
= options
.getOrDefault("--dep-categories", new ArrayList
<>());
101 List
<String
> a2Bases
= options
.getOrDefault("--a2-bases", new ArrayList
<>());
102 if (a2Bases
.isEmpty()) {
103 a2Bases
.add(a2Output
.toString());
106 List
<String
> compilerArgs
= new ArrayList
<>();
108 Path ecjArgs
= argeoBuildBase
.resolve("ecj.args");
109 compilerArgs
.add("@" + ecjArgs
);
112 if (!a2Categories
.isEmpty()) {
113 compilerArgs
.add("-cp");
114 StringJoiner classPath
= new StringJoiner(File
.pathSeparator
);
115 for (String a2Base
: a2Bases
) {
116 for (String a2Category
: a2Categories
) {
117 Path a2Dir
= Paths
.get(a2Base
).resolve(a2Category
);
118 if (!Files
.exists(a2Dir
))
119 Files
.createDirectories(a2Dir
);
120 for (Path jarP
: Files
.newDirectoryStream(a2Dir
,
121 (p
) -> p
.getFileName().toString().endsWith(".jar"))) {
122 classPath
.add(jarP
.toString());
126 compilerArgs
.add(classPath
.toString());
130 for (String bundle
: bundles
) {
131 StringBuilder sb
= new StringBuilder();
132 sb
.append(execDirectory
.resolve(bundle
).resolve("src"));
134 compilerArgs
.add(sb
.toString());
135 sb
= new StringBuilder();
136 sb
.append(buildBase
.resolve(bundle
).resolve("bin"));
138 compilerArgs
.add(sb
.toString());
141 // System.out.println(compilerArgs);
143 CompilationProgress compilationProgress
= new CompilationProgress() {
145 long currentChunk
= 0;
147 long chunksCount
= 80;
150 public void worked(int workIncrement
, int remainingWork
) {
151 long chunk
= ((totalWork
- remainingWork
) * chunksCount
) / totalWork
;
152 if (chunk
!= currentChunk
) {
153 currentChunk
= chunk
;
154 for (long i
= 0; i
< currentChunk
; i
++) {
155 System
.out
.print("#");
157 for (long i
= currentChunk
; i
< chunksCount
; i
++) {
158 System
.out
.print("-");
160 System
.out
.print("\r");
162 if (remainingWork
== 0)
163 System
.out
.print("\n");
167 public void setTaskName(String name
) {
171 public boolean isCanceled() {
180 public void begin(int remainingWork
) {
181 this.totalWork
= remainingWork
;
184 // Use Main instead of BatchCompiler to workaround the fact that
185 // org.eclipse.jdt.core.compiler.batch is not exported
186 boolean success
= org
.eclipse
.jdt
.internal
.compiler
.batch
.Main
.compile(
187 compilerArgs
.toArray(new String
[compilerArgs
.size()]), new PrintWriter(System
.out
),
188 new PrintWriter(System
.err
), (CompilationProgress
) compilationProgress
);
194 void bundle(Map
<String
, List
<String
>> options
) throws IOException
{
195 List
<String
> bundles
= options
.get("--bundles");
196 Objects
.requireNonNull(bundles
, "--bundles argument must be set");
197 if (bundles
.isEmpty())
200 List
<String
> categories
= options
.get("--category");
201 Objects
.requireNonNull(bundles
, "--bundles argument must be set");
202 if (categories
.size() != 1)
203 throw new IllegalArgumentException("One and only one category must be specified");
204 String category
= categories
.get(0);
207 for (String bundle
: bundles
) {
208 createBundle(bundle
, category
);
215 void createBundle(String bundle
, String category
) throws IOException
{
216 Path source
= execDirectory
.resolve(bundle
);
217 Path compiled
= buildBase
.resolve(bundle
);
218 String bundleSymbolicName
= source
.getFileName().toString();
221 Properties properties
= new Properties();
222 Path argeoBnd
= argeoBuildBase
.resolve("argeo.bnd");
223 try (InputStream in
= Files
.newInputStream(argeoBnd
)) {
226 // FIXME make it configurable
227 Path branchBnd
= sdkSrcBase
.resolve("cnf/unstable.bnd");
228 try (InputStream in
= Files
.newInputStream(branchBnd
)) {
232 Path bndBnd
= source
.resolve("bnd.bnd");
233 try (InputStream in
= Files
.newInputStream(bndBnd
)) {
238 properties
.put("Bundle-SymbolicName", bundleSymbolicName
);
240 // Calculate MANIFEST
241 Path binP
= compiled
.resolve("bin");
242 if (!Files
.exists(binP
))
243 Files
.createDirectories(binP
);
245 try (Analyzer bndAnalyzer
= new Analyzer()) {
246 bndAnalyzer
.setProperties(properties
);
247 Jar jar
= new Jar(bundleSymbolicName
, binP
.toFile());
248 bndAnalyzer
.setJar(jar
);
249 manifest
= bndAnalyzer
.calcManifest();
251 // keys: for (Object key : manifest.getMainAttributes().keySet()) {
252 // System.out.println(key + ": " + manifest.getMainAttributes().getValue(key.toString()));
254 } catch (Exception e
) {
255 throw new RuntimeException("Bnd analysis of " + compiled
+ " failed", e
);
258 String major
= properties
.getProperty("MAJOR");
259 Objects
.requireNonNull(major
, "MAJOR must be set");
260 String minor
= properties
.getProperty("MINOR");
261 Objects
.requireNonNull(minor
, "MINOR must be set");
264 Path manifestP
= compiled
.resolve("META-INF/MANIFEST.MF");
265 Files
.createDirectories(manifestP
.getParent());
266 try (OutputStream out
= Files
.newOutputStream(manifestP
)) {
271 // Path manifestP = compiled.resolve("META-INF/MANIFEST.MF");
272 // if (!Files.exists(manifestP))
273 // throw new IllegalStateException("Manifest " + manifestP + " not found");
274 // Manifest manifest;
275 // try (InputStream in = Files.newInputStream(manifestP)) {
276 // manifest = new Manifest(in);
277 // } catch (IOException e) {
278 // throw new IllegalStateException("Cannot read manifest " + manifestP, e);
282 List
<PathMatcher
> excludes
= new ArrayList
<>();
283 Path excludesP
= argeoBuildBase
.resolve("excludes.txt");
284 for (String line
: Files
.readAllLines(excludesP
)) {
285 PathMatcher pathMatcher
= excludesP
.getFileSystem().getPathMatcher("glob:" + line
);
286 excludes
.add(pathMatcher
);
289 Path bundleParent
= Paths
.get(bundle
).getParent();
290 Path a2JarDirectory
= bundleParent
!= null ? a2Output
.resolve(bundleParent
).resolve(category
)
291 : a2Output
.resolve(category
);
292 Path jarP
= a2JarDirectory
.resolve(compiled
.getFileName() + "." + major
+ "." + minor
+ ".jar");
293 Files
.createDirectories(jarP
.getParent());
295 try (JarOutputStream jarOut
= new JarOutputStream(Files
.newOutputStream(jarP
), manifest
)) {
296 // add all classes first
297 // Path binP = compiled.resolve("bin");
298 Files
.walkFileTree(binP
, new SimpleFileVisitor
<Path
>() {
301 public FileVisitResult
visitFile(Path file
, BasicFileAttributes attrs
) throws IOException
{
302 jarOut
.putNextEntry(new JarEntry(binP
.relativize(file
).toString()));
303 Files
.copy(file
, jarOut
);
304 return FileVisitResult
.CONTINUE
;
309 Files
.walkFileTree(source
, new SimpleFileVisitor
<Path
>() {
312 public FileVisitResult
preVisitDirectory(Path dir
, BasicFileAttributes attrs
) throws IOException
{
313 Path relativeP
= source
.relativize(dir
);
314 for (PathMatcher exclude
: excludes
)
315 if (exclude
.matches(relativeP
))
316 return FileVisitResult
.SKIP_SUBTREE
;
318 return FileVisitResult
.CONTINUE
;
322 public FileVisitResult
visitFile(Path file
, BasicFileAttributes attrs
) throws IOException
{
323 Path relativeP
= source
.relativize(file
);
324 for (PathMatcher exclude
: excludes
)
325 if (exclude
.matches(relativeP
))
326 return FileVisitResult
.CONTINUE
;
327 JarEntry entry
= new JarEntry(relativeP
.toString());
328 jarOut
.putNextEntry(entry
);
329 Files
.copy(file
, jarOut
);
330 return FileVisitResult
.CONTINUE
;
336 // TODO add effective BND, Eclipse project file, etc., in order to be able to
338 Path srcP
= source
.resolve("src");
339 Files
.walkFileTree(srcP
, new SimpleFileVisitor
<Path
>() {
342 public FileVisitResult
visitFile(Path file
, BasicFileAttributes attrs
) throws IOException
{
343 jarOut
.putNextEntry(new JarEntry("OSGI-OPT/src/" + srcP
.relativize(file
).toString()));
344 Files
.copy(file
, jarOut
);
345 return FileVisitResult
.CONTINUE
;
353 private Path
findSdkMk(Path directory
) {
354 Path sdkMkP
= directory
.resolve(SDK_MK
);
355 if (Files
.exists(sdkMkP
)) {
356 return sdkMkP
.toAbsolutePath();
358 if (directory
.getParent() == null)
360 return findSdkMk(directory
.getParent());
364 public static void main(String
... args
) {
366 if (args
.length
== 0)
367 throw new IllegalArgumentException("At least an action must be provided");
369 String action
= args
[actionIndex
];
370 if (args
.length
> actionIndex
+ 1 && !args
[actionIndex
+ 1].startsWith("-"))
371 throw new IllegalArgumentException(
372 "Action " + action
+ " must be followed by an option: " + Arrays
.asList(args
));
374 Map
<String
, List
<String
>> options
= new HashMap
<>();
375 String currentOption
= null;
376 for (int i
= actionIndex
+ 1; i
< args
.length
; i
++) {
377 if (args
[i
].startsWith("-")) {
378 currentOption
= args
[i
];
379 if (!options
.containsKey(currentOption
))
380 options
.put(currentOption
, new ArrayList
<>());
383 options
.get(currentOption
).add(args
[i
]);
387 Make argeoMake
= new Make();
389 case "compile" -> argeoMake
.compile(options
);
390 case "bundle" -> argeoMake
.bundle(options
);
391 case "all" -> argeoMake
.all(options
);
393 default -> throw new IllegalArgumentException("Unkown action: " + action
);
396 long jvmUptime
= ManagementFactory
.getRuntimeMXBean().getUptime();
397 System
.out
.println("Completed after " + jvmUptime
+ " ms");
398 } catch (Exception e
) {