This repository has been archived by the owner on Apr 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 62
/
CeylonAssembleTool.java
245 lines (227 loc) · 10.6 KB
/
CeylonAssembleTool.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package com.redhat.ceylon.tools.assemble;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import com.redhat.ceylon.cmr.api.ModuleQuery;
import com.redhat.ceylon.cmr.ceylon.loader.ModuleGraph;
import com.redhat.ceylon.cmr.impl.IOUtils;
import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.common.FileUtil;
import com.redhat.ceylon.common.JVMModuleUtil;
import com.redhat.ceylon.common.ModuleSpec;
import com.redhat.ceylon.common.ModuleUtil;
import com.redhat.ceylon.common.Versions;
import com.redhat.ceylon.common.config.DefaultToolOptions;
import com.redhat.ceylon.common.tool.Argument;
import com.redhat.ceylon.common.tool.Description;
import com.redhat.ceylon.common.tool.Option;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tool.ToolUsageError;
import com.redhat.ceylon.tools.moduleloading.ModuleLoadingTool;
/**
* @author Tako Schotanus (tako@ceylon-lang.org)
*/
@Summary("Generate a Ceylon assemble for a given module")
@Description("Generate an executable _assemble_ which contains the given module and all its run-time"
+ " dependencies, including the Ceylon run-time, which makes that jar self-sufficient and"
+ " executable by `java` as if the Ceylon module was run by `ceylon run`."
)
public class CeylonAssembleTool extends ModuleLoadingTool {
private List<ModuleSpec> modules;
private boolean force;
private File out;
private final List<String> excludedModules = new ArrayList<>();
/** The (Ceylon) name of the functional to run, e.g. {@code foo.bar::baz} */
private String run;
private boolean includeLanguage;
public static final String CEYLON_ASSEMBLY_SUFFIX = ".cas";
@Argument(order = 1, argumentName="module", multiplicity="+")
public void setModules(List<String> modules) {
setModuleSpecs(ModuleSpec.parseEachList(modules));
}
public void setModuleSpecs(List<ModuleSpec> modules) {
this.modules = modules;
}
@OptionArgument(longName = "run", argumentName = "toplevel")
@Description("Specifies the fully qualified name of a toplevel method or class with no parameters. " +
"The format is: `qualified.package.name::classOrMethodName` with `::` acting as separator " +
"between the package name and the toplevel class or method name (defaults to `{module}::run`).")
public void setRun(String run) {
this.run = run;
}
@Description("Target assemble file (defaults to `{name}-{version}.jar`).")
@OptionArgument(shortName = 'o', argumentName="file")
public void setOut(File out) {
this.out = out;
}
@OptionArgument(argumentName="moduleOrFile", shortName='x')
@Description("Excludes modules from the resulting far jat. Can be a module name or " +
"a file containing module names. Can be specified multiple times. Note that "+
"this excludes the module from the resulting assemble, but if your modules require that "+
"module to be present at runtime it will still be required and may cause your "+
"application to fail to start if it is not provided at runtime.")
public void setExcludeModule(List<String> exclusions) {
for (String each : exclusions) {
File xFile = new File(each);
if (xFile.exists() && xFile.isFile()) {
try (BufferedReader reader = new BufferedReader(new FileReader(xFile))) {
String line;
while ((line = reader.readLine()) != null) {
this.excludedModules.add(line);
}
} catch (IOException e) {
throw new ToolUsageError(CeylonAssembleMessages.msg("exclude.file.failure", each),
e);
}
} else {
this.excludedModules.add(each);
}
}
}
@Option(longName="force")
@Description("Ignore errors about conflicting modules.")
public void setForce(boolean force) {
this.force = force;
}
@Option
@Description("Include the language module and its dependencies to the assembly."
+ " Using this option ensures that the assembly can be run stand-alone"
+ " using Java without having Ceylon installed")
public void setIncludeLanguage(boolean includeLanguage) {
this.includeLanguage = includeLanguage;
}
@Override
public void run() throws Exception {
String firstModuleName = null, firstModuleVersion = null;
for (ModuleSpec module : modules) {
String moduleName = module.getName();
String version = checkModuleVersionsOrShowSuggestions(
moduleName,
module.isVersioned() ? module.getVersion() : null,
ModuleQuery.Type.JVM,
Versions.JVM_BINARY_MAJOR_VERSION,
Versions.JVM_BINARY_MINOR_VERSION,
null, null, // JS binary but don't care since JVM
null);
if(version == null)
return;
if(firstModuleName == null){
firstModuleName = moduleName;
firstModuleVersion = version;
}
loadModule(null, moduleName, version);
if(!force)
errorOnConflictingModule(moduleName, version);
}
loader.resolve();
String versionSuffix = firstModuleVersion != null && !firstModuleVersion.isEmpty()
? "-"+firstModuleVersion
: "";
File outputCas = applyCwd(out != null ? out : new File(firstModuleName + versionSuffix + CEYLON_ASSEMBLY_SUFFIX));
if(outputCas.getParentFile() != null && !outputCas.getParentFile().exists()){
FileUtil.mkdirs(outputCas.getParentFile());
}
if(outputCas.exists()){
FileUtil.delete(outputCas);
}
final Set<String> added = new HashSet<>();
Manifest manifest = new Manifest();
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.putValue("Manifest-Version", "1.0");
mainAttributes.putValue("Created-By", "Ceylon assemble for module "+firstModuleName+"/"+firstModuleVersion);
mainAttributes.putValue(Constants.ATTR_ASSEMBLY_MAIN_MODULE, ModuleUtil.makeModuleName(firstModuleName, firstModuleVersion));
if (run != null) {
mainAttributes.putValue(Constants.ATTR_ASSEMBLY_RUN, run);
}
mainAttributes.putValue(Constants.ATTR_ASSEMBLY_REPOSITORY, "modules");
File ovrFile = getOverridesFile();
if (ovrFile != null) {
mainAttributes.putValue(Constants.ATTR_ASSEMBLY_OVERRIDES, ovrFile.getName());
}
if (includeLanguage) {
String className = JVMModuleUtil.javaClassNameFromCeylon(firstModuleName, run != null ? run : (firstModuleName + "::run"));
mainAttributes.putValue("Main-Class", "com.redhat.ceylon.tools.assemble.CeylonAssemblyRunner");
mainAttributes.putValue(Constants.ATTR_ASSEMBLY_MAIN_CLASS, className);
}
added.add("META-INF/");
added.add("META-INF/MANIFEST.MF");
try(ZipOutputStream zipFile = new JarOutputStream(new FileOutputStream(outputCas), manifest)){
if (ovrFile != null) {
// Copy the overrides.xml file to the output CAS
try (InputStream is = new FileInputStream(ovrFile)) {
zipFile.putNextEntry(new ZipEntry(ovrFile.getName()));
IOUtils.copyStream(is, zipFile, true, false);
}
}
if (includeLanguage) {
// Copy the CeylonAssemblyRunner class and dependencies to the output CAS
String prefix = CeylonAssemblyRunner.class.getName().replace('.', '/');
String[] postfixes = {
"",
"$CeylonAssemblyClassLoader",
"$CeylonAssemblyClassLoader$1"
};
for (String postfix : postfixes) {
String clsName = prefix + postfix + ".class";
try (InputStream is = CeylonAssemblyRunner.class.getResourceAsStream("/" + clsName)) {
zipFile.putNextEntry(new ZipEntry(clsName));
IOUtils.copyStream(is, zipFile, true, false);
}
}
}
loader.visitModules(new ModuleGraph.Visitor() {
@Override
public void visit(ModuleGraph.Module module) {
if(module.artifact != null){
File file = module.artifact.artifact();
try{
if(file != null){
if(isVerbose()){
append(file.getAbsolutePath());
newline();
}
try (InputStream is = new FileInputStream(file)) {
String name = "modules/" + ModuleUtil.moduleToPath(module.name) + "/" + module.version + "/" + file.getName();
zipFile.putNextEntry(new ZipEntry(name));
IOUtils.copyStream(is, zipFile, true, false);
}
}
}catch(IOException x){
// lame
throw new RuntimeException(x);
}
}
}
});
zipFile.flush();
}
flush();
}
@Override
protected boolean shouldExclude(String moduleName, String version) {
return super.shouldExclude(moduleName, version) ||
this.excludedModules.contains(moduleName) ||
(!includeLanguage && "ceylon.language".equals(moduleName));
}
private File getOverridesFile() {
String path = (overrides != null) ? overrides : DefaultToolOptions.getDefaultOverrides();
if (path != null) {
return applyCwd(new File(path));
}
return null;
}
}