This repository has been archived by the owner on Apr 13, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First stab at implementing #6712 ceylon assemble
- Loading branch information
Showing
7 changed files
with
504 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
; Plugin definition for the "ceylon assemble" tool | ||
summary=Generate a Ceylon executable jar for a given module | ||
module=com.redhat.ceylon.tools/@ceylon-version@ | ||
class=com.redhat.ceylon.tools.assemble.CeylonAssembleTool |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
tools/src/com/redhat/ceylon/tools/assemble/CeylonAssembleMessages.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* Copyright Red Hat Inc. and/or its affiliates and other contributors | ||
* as indicated by the authors tag. All rights reserved. | ||
* | ||
* This copyrighted material is made available to anyone wishing to use, | ||
* modify, copy, or redistribute it subject to the terms and conditions | ||
* of the GNU General Public License version 2. | ||
* | ||
* This particular file is subject to the "Classpath" exception as provided in the | ||
* LICENSE file that accompanied this code. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT A | ||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | ||
* PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||
* You should have received a copy of the GNU General Public License, | ||
* along with this distribution; if not, write to the Free Software | ||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | ||
* MA 02110-1301, USA. | ||
*/ | ||
package com.redhat.ceylon.tools.assemble; | ||
|
||
import java.util.ResourceBundle; | ||
|
||
import com.redhat.ceylon.common.Messages; | ||
|
||
public class CeylonAssembleMessages extends Messages { | ||
|
||
public static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(CeylonAssembleMessages.class.getPackage().getName() + ".resources.messages"); | ||
|
||
public static String msg(String msgKey, Object... msgArgs) { | ||
return msg(RESOURCE_BUNDLE, msgKey, msgArgs); | ||
} | ||
|
||
} |
245 changes: 245 additions & 0 deletions
245
tools/src/com/redhat/ceylon/tools/assemble/CeylonAssembleTool.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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; | ||
} | ||
} |
Oops, something went wrong.