]> git.argeo.org Git - cc0/argeo-build.git/blob - java/org/argeo/build/Make.java
Introduce Java builder
[cc0/argeo-build.git] / java / org / argeo / build / Make.java
1 package org.argeo.build;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.nio.file.FileVisitResult;
7 import java.nio.file.Files;
8 import java.nio.file.Path;
9 import java.nio.file.PathMatcher;
10 import java.nio.file.Paths;
11 import java.nio.file.SimpleFileVisitor;
12 import java.nio.file.attribute.BasicFileAttributes;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Objects;
20 import java.util.Properties;
21 import java.util.StringTokenizer;
22 import java.util.jar.JarEntry;
23 import java.util.jar.JarOutputStream;
24 import java.util.jar.Manifest;
25
26 import aQute.bnd.osgi.Analyzer;
27 import aQute.bnd.osgi.Jar;
28
29 public class Make {
30 private final static String SDK_MK = "sdk.mk";
31
32 final Path execDirectory;
33 final Path sdkSrcBase;
34 final Path argeoBuildBase;
35 final Path sdkBuildBase;
36 final Path buildBase;
37
38 public Make() throws IOException {
39 execDirectory = Paths.get(System.getProperty("user.dir"));
40 Path sdkMkP = findSdkMk(execDirectory);
41 Objects.requireNonNull(sdkMkP, "No " + SDK_MK + " found under " + execDirectory);
42
43 Map<String, String> context = new HashMap<>();
44 List<String> sdkMkLines = Files.readAllLines(sdkMkP);
45 lines: for (String line : sdkMkLines) {
46 StringTokenizer st = new StringTokenizer(line, " :=");
47 if (!st.hasMoreTokens())
48 continue lines;
49 String key = st.nextToken();
50 if (!st.hasMoreTokens())
51 continue lines;
52 String value = st.nextToken();
53 context.put(key, value);
54 }
55
56 sdkSrcBase = Paths.get(context.computeIfAbsent("SDK_SRC_BASE", (key) -> {
57 throw new IllegalStateException(key + " not found");
58 })).toAbsolutePath();
59 argeoBuildBase = sdkSrcBase.resolve("sdk/argeo-build");
60
61 sdkBuildBase = Paths.get(context.computeIfAbsent("SDK_BUILD_BASE", (key) -> {
62 throw new IllegalStateException(key + " not found");
63 })).toAbsolutePath();
64 buildBase = sdkBuildBase.resolve(sdkSrcBase.getFileName());
65 }
66
67 /*
68 * ACTIONS
69 */
70
71 void bundle(Map<String, List<String>> options) throws IOException {
72 // generate manifests
73 subDirs: for (Path subDir : Files.newDirectoryStream(buildBase, (p) -> Files.isDirectory(p))) {
74 String bundleSymbolicName = subDir.getFileName().toString();
75 if (!bundleSymbolicName.contains("."))
76 continue subDirs;
77 generateManifest(bundleSymbolicName, subDir);
78 }
79
80 // create jars
81 subDirs: for (Path subDir : Files.newDirectoryStream(buildBase, (p) -> Files.isDirectory(p))) {
82 String bundleSymbolicName = subDir.getFileName().toString();
83 if (!bundleSymbolicName.contains("."))
84 continue subDirs;
85 Path source = sdkSrcBase.resolve(subDir.getFileName());
86 if (!Files.exists(source))
87 continue subDirs;
88 Path jarP = buildBase.resolve(subDir.getFileName() + ".jar");
89 createJar(source, subDir, jarP);
90 }
91
92 }
93
94 /*
95 * BND
96 */
97
98 void generateManifest(String bundleSymbolicName, Path compiled) throws IOException {
99 Properties properties = new Properties();
100 Path argeoBnd = argeoBuildBase.resolve("argeo.bnd");
101 try (InputStream in = Files.newInputStream(argeoBnd)) {
102 properties.load(in);
103 }
104 // FIXME make it configurable
105 Path branchBnd = sdkSrcBase.resolve("cnf/unstable.bnd");
106 try (InputStream in = Files.newInputStream(branchBnd)) {
107 properties.load(in);
108 }
109
110 Path bndBnd = compiled.resolve("bnd.bnd");
111 try (InputStream in = Files.newInputStream(bndBnd)) {
112 properties.load(in);
113 }
114
115 // Normalise
116 properties.put("Bundle-SymbolicName", bundleSymbolicName);
117
118 // Calculate MANIFEST
119 Path binP = compiled.resolve("bin");
120 Manifest manifest;
121 try (Analyzer bndAnalyzer = new Analyzer()) {
122 bndAnalyzer.setProperties(properties);
123 Jar jar = new Jar(bundleSymbolicName, binP.toFile());
124 bndAnalyzer.setJar(jar);
125 manifest = bndAnalyzer.calcManifest();
126
127 // keys: for (Object key : manifest.getMainAttributes().keySet()) {
128 // System.out.println(key + ": " + manifest.getMainAttributes().getValue(key.toString()));
129 // }
130 } catch (Exception e) {
131 throw new RuntimeException("Bnd analysis of " + compiled + " failed", e);
132 }
133
134 // Write manifest
135 Path manifestP = compiled.resolve("META-INF/MANIFEST.MF");
136 Files.createDirectories(manifestP.getParent());
137 try (OutputStream out = Files.newOutputStream(manifestP)) {
138 manifest.write(out);
139 }
140 }
141
142 /*
143 * JAR PACKAGING
144 */
145 void createJar(Path source, Path compiled, Path jarP) throws IOException {
146 // Load manifest
147 Path manifestP = compiled.resolve("META-INF/MANIFEST.MF");
148 if (!Files.exists(manifestP))
149 throw new IllegalStateException("Manifest " + manifestP + " not found");
150 Manifest manifest;
151 try (InputStream in = Files.newInputStream(manifestP)) {
152 manifest = new Manifest(in);
153 } catch (IOException e) {
154 throw new IllegalStateException("Cannot read manifest " + manifestP, e);
155 }
156
157 // Load excludes
158 List<PathMatcher> excludes = new ArrayList<>();
159 Path excludesP = argeoBuildBase.resolve("excludes.txt");
160 for (String line : Files.readAllLines(excludesP)) {
161 PathMatcher pathMatcher = excludesP.getFileSystem().getPathMatcher("glob:" + line);
162 excludes.add(pathMatcher);
163 }
164
165 Files.createDirectories(jarP.getParent());
166 try (JarOutputStream jarOut = new JarOutputStream(Files.newOutputStream(jarP), manifest)) {
167 // add all classes first
168 Path binP = compiled.resolve("bin");
169 Files.walkFileTree(binP, new SimpleFileVisitor<Path>() {
170
171 @Override
172 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
173 jarOut.putNextEntry(new JarEntry(binP.relativize(file).toString()));
174 Files.copy(file, jarOut);
175 return FileVisitResult.CONTINUE;
176 }
177 });
178
179 // add resources
180 Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
181
182 @Override
183 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
184 Path relativeP = source.relativize(dir);
185 for (PathMatcher exclude : excludes)
186 if (exclude.matches(relativeP))
187 return FileVisitResult.SKIP_SUBTREE;
188
189 return FileVisitResult.CONTINUE;
190 }
191
192 @Override
193 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
194 Path relativeP = source.relativize(file);
195 for (PathMatcher exclude : excludes)
196 if (exclude.matches(relativeP))
197 return FileVisitResult.CONTINUE;
198 JarEntry entry = new JarEntry(relativeP.toString());
199 jarOut.putNextEntry(entry);
200 Files.copy(file, jarOut);
201 return FileVisitResult.CONTINUE;
202 }
203
204 });
205
206 // add sources
207 // TODO add effective BND, Eclipse project file, etc., in order to be able to
208 // repackage
209 Path srcP = source.resolve("src");
210 Files.walkFileTree(srcP, new SimpleFileVisitor<Path>() {
211
212 @Override
213 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
214 jarOut.putNextEntry(new JarEntry("OSGI-OPT/src/" + srcP.relativize(file).toString()));
215 Files.copy(file, jarOut);
216 return FileVisitResult.CONTINUE;
217 }
218 });
219
220 }
221
222 }
223
224 private Path findSdkMk(Path directory) {
225 Path sdkMkP = directory.resolve(SDK_MK);
226 if (Files.exists(sdkMkP)) {
227 return sdkMkP.toAbsolutePath();
228 }
229 if (directory.getParent() == null)
230 return null;
231 return findSdkMk(directory.getParent());
232
233 }
234
235 public static void main(String... args) throws IOException {
236 if (args.length == 0)
237 throw new IllegalArgumentException("At least an action must be provided");
238 int actionIndex = 0;
239 String action = args[actionIndex];
240 if (args.length > actionIndex + 1 && args[actionIndex + 1].startsWith("-"))
241 throw new IllegalArgumentException(
242 "Action " + action + " must be followed by an option: " + Arrays.asList(args));
243
244 Map<String, List<String>> options = new HashMap<>();
245 String currentOption = null;
246 List<String> currentOptionValues = null;
247 for (int i = actionIndex + 1; i < args.length; i++) {
248 if (args[i].startsWith("-")) {
249 if (currentOption != null) {
250 assert currentOptionValues != null;
251 if (!options.containsKey(currentOption))
252 options.put(currentOption, new ArrayList<>());
253 options.get(currentOption).addAll(currentOptionValues);
254 }
255 currentOption = args[i];
256 currentOptionValues = new ArrayList<>();
257 } else {
258 currentOptionValues.add(args[i]);
259 }
260 }
261 Make argeoBuild = new Make();
262 switch (action) {
263 case "bundle" -> argeoBuild.bundle(options);
264 default -> throw new IllegalArgumentException("Unkown action: " + action);
265 }
266
267 }
268 }