com.redhat.ceylon.compiler.CeylonCompileTool Maven / Gradle / Ivy
/*
* 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.compiler;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import com.redhat.ceylon.cmr.api.ModuleQuery;
import com.redhat.ceylon.cmr.api.ModuleQuery.Type;
import com.redhat.ceylon.cmr.api.ModuleVersionDetails;
import com.redhat.ceylon.cmr.ceylon.ShaSigner;
import com.redhat.ceylon.common.Backend;
import com.redhat.ceylon.common.Backends;
import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.common.FileUtil;
import com.redhat.ceylon.common.ModuleSpec;
import com.redhat.ceylon.common.ModuleUtil;
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.EnumUtil;
import com.redhat.ceylon.common.tool.Hidden;
import com.redhat.ceylon.common.tool.NonFatalToolMessage;
import com.redhat.ceylon.common.tool.Option;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.ParsedBy;
import com.redhat.ceylon.common.tool.RemainingSections;
import com.redhat.ceylon.common.tool.StandardArgumentParsers;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tool.ToolUsageError;
import com.redhat.ceylon.common.tools.CeylonTool;
import com.redhat.ceylon.common.tools.ModuleWildcardsHelper;
import com.redhat.ceylon.common.tools.OutputRepoUsingTool;
import com.redhat.ceylon.common.tools.RepoUsingTool;
import com.redhat.ceylon.common.tools.SourceArgumentsResolver;
import com.redhat.ceylon.common.tools.SourceDependencyResolver;
import com.redhat.ceylon.compiler.java.launcher.Main;
import com.redhat.ceylon.compiler.java.launcher.Main.ExitState.CeylonState;
import com.redhat.ceylon.compiler.typechecker.analyzer.Warning;
import com.redhat.ceylon.langtools.tools.javac.main.Main.Result;
import com.redhat.ceylon.langtools.tools.javac.util.Context;
import com.redhat.ceylon.langtools.tools.javac.util.Log;
import com.redhat.ceylon.langtools.tools.javac.util.Options;
@Summary("Compiles Ceylon and Java source code and directly produces module " +
"and source archives in a module repository.")
@Description("The default module repositories are `modules` and `" +
Constants.REPO_URL_CEYLON+"`, while the default source directory is `source` " +
"and the default resource directory is `resource`. " +
"The default output module repository is `modules`." +
"\n\n" +
"The `` arguments can be either module names (without versions) " +
"or file paths specifying the Ceylon or Java source code to compile." +
"\n\n" +
"When `` specifies a module the compiler searches for " +
"compilation units and resource files belonging to the specified modules " +
"in the specified source and resource directories. " +
"For each specified module, the compiler generates a module archive, " +
"source archive, and their checksum files in the specified output module " +
"repository." +
"\n\n"+
"When `` specifies a source file only that file is compiled and " +
"the module archive is created or updated with the .class files produced. " +
"The source file path is treated as relative to the current directory " +
"(it still needs to be located either in the default source folder or in " +
"any folder defined by the configuration file or `--source` options!)."+
"\n\n" +
"When `` specifies a resource file only that file is added to " +
"the module archive. " +
"The resource file path is treated as relative to the current directory " +
"(it still needs to be located either in the default resource folder or in " +
"any folder defined by the configuration file or `--resource` options!)."+
"\n\n" +
"All program elements imported by a compilation unit must belong to the " +
"same module as the compilation unit, or must belong to a module that " +
"is explicitly imported in the module descriptor." +
"\n\n" +
"The compiler searches for dependencies in the following locations:" +
"\n\n" +
"* module archives in the specified repositories,\n"+
"* source archives in the specified repositories, and\n"+
"* module directories in the specified source directories.\n")
@RemainingSections(
RepoUsingTool.DOCSECTION_INCLUDE_DEPS +
"\n\n" +
OutputRepoUsingTool.DOCSECTION_CONFIG_COMPILER +
"\n\n" +
OutputRepoUsingTool.DOCSECTION_REPOSITORIES +
"\n\n" +
"## Specifying `javac` options" +
"\n\n" +
"It is possible to pass options to the `javac` compiler by prefixing them " +
"with `--javac=` and separating the javac option from its argument (if any) " +
"using another `=`. For example, the option `--javac=-g:none` is equivalent to `javac`'s `-g:none`" +
"\n\n" +
"Execute `ceylon compile --javac=-help` for a list of the standard javac " +
"options, and ceylon compile --javac=-X for a list of the non-standard javac " +
"options." +
"\n\n" +
"**Important note**: There is no guarantee that any particular `javac` " +
"option or combination of options will work, or continue to work in " +
"future releases.")
public class CeylonCompileTool extends OutputRepoUsingTool {
private static final class Helper extends com.redhat.ceylon.langtools.tools.javac.main.OptionHelper {
String lastError = null;
private final HashMap options = new HashMap();
private final HashMap> multiOptions = new HashMap>();
@Override
public String get(com.redhat.ceylon.langtools.tools.javac.main.Option option) {
return options.get(option.text);
}
@Override
public void put(String name, String value) {
options.put(name, value);
}
@Override
public void remove(String name) {
options.remove(name);
}
@Override
public Log getLog() {
return null;
}
@Override
public String getOwnName() {
return null;
}
@Override
protected void error(String key, Object... args) {
lastError = Main.getLocalizedString(key, args);
}
@Override
protected void addFile(File f) {
}
@Override
protected void addClassName(String s) {
}
@Override
public List getMulti(com.redhat.ceylon.langtools.tools.javac.main.Option option) {
return multiOptions.get(option.text);
}
@Override
public void addMulti(String name, String value) {
List list = multiOptions.get(name);
if (list == null) {
list = new ArrayList(2);
multiOptions.put(name, list);
}
list.add(value);
}
}
private Helper helper = new Helper();
private List sources = DefaultToolOptions.getCompilerSourceDirs();
private List resources = DefaultToolOptions.getCompilerResourceDirs();
private List modulesOrFiles = DefaultToolOptions.getCompilerModules(Backend.Java);
private boolean continueOnErrors;
private boolean progress = DefaultToolOptions.getCompilerProgress();
private List javac = DefaultToolOptions.getCompilerJavac();
private String encoding;
private String includeDependencies;
private boolean incremental = DefaultToolOptions.getCompilerIncremental();
private String resourceRoot = DefaultToolOptions.getCompilerResourceRootName();
private boolean noOsgi = DefaultToolOptions.getCompilerNoOsgi();
private String osgiProvidedBundles = DefaultToolOptions.getCompilerOsgiProvidedBundles();
private boolean noPom = DefaultToolOptions.getCompilerNoPom();
private boolean pack200 = DefaultToolOptions.getCompilerPack200();
private EnumSet suppressWarnings = EnumUtil.enumsFromPossiblyInvalidStrings(Warning.class, DefaultToolOptions.getCompilerSuppressWarnings());
private boolean flatClasspath = DefaultToolOptions.getDefaultFlatClasspath();
private boolean autoExportMavenDependencies = DefaultToolOptions.getDefaultAutoExportMavenDependencies();
private boolean fullyExportMavenDependencies = DefaultToolOptions.getDefaultFullyExportMavenDependencies();
private boolean jigsaw = DefaultToolOptions.getCompilerGenerateModuleInfo();
private Long targetVersion = DefaultToolOptions.getCompilerTargetVersion();
private boolean ee = DefaultToolOptions.getCompilerEe();
private List eeImport = DefaultToolOptions.getCompilerEeImport();
private List eeAnnotation = DefaultToolOptions.getCompilerEeAnnotation();
private ModuleSpec jdkProvider;
{
String jdkProvider = DefaultToolOptions.getCompilerJdkProvider();
this.jdkProvider = jdkProvider != null ? ModuleSpec.parse(jdkProvider) : null;
}
private List aptModules;
{
String[] aptModules = DefaultToolOptions.getCompilerAptModules();
if(aptModules != null)
setAptModule(Arrays.asList(aptModules));
}
public CeylonCompileTool() {
super(CeylonCompileMessages.RESOURCE_BUNDLE);
}
@OptionArgument(longName="jdk-provider", argumentName="module")
@Description("Specifies the name of the module providing the JDK (default: the underlying JDK).")
public void setJdkProvider(String jdkProvider) {
setJdkProviderSpec(ModuleSpec.parse(jdkProvider));
}
public void setJdkProviderSpec(ModuleSpec jdkProvider) {
this.jdkProvider = jdkProvider;
}
@OptionArgument(longName="apt", argumentName="module")
@Description("Specifies the list of modules to use as Java annotation-processing modules (default: none). Experimental.")
public void setAptModule(List aptModules) {
if(aptModules != null){
this.aptModules = new ArrayList(aptModules.size());
for(String mod : aptModules)
this.aptModules.add(ModuleSpec.parse(mod));
}else{
this.aptModules = null;
}
}
@Option(longName="flat-classpath")
@Description("Launches the Ceylon module using a flat classpath.")
public void setFlatClasspath(boolean flatClasspath) {
this.flatClasspath = flatClasspath;
}
@Option(longName="auto-export-maven-dependencies")
@Description("When using JBoss Modules (the default), treats all module dependencies between " +
"Maven modules as shared.")
public void setAutoExportMavenDependencies(boolean autoExportMavenDependencies) {
this.autoExportMavenDependencies = autoExportMavenDependencies;
}
@Option(longName="fully-export-maven-dependencies")
@Description("When using JBoss Modules (the default), treats all module dependencies between " +
"Maven modules as shared, even to Ceylon modules.")
public void setFullyExportMavenDependencies(boolean fullyExportMavenDependencies) {
this.fullyExportMavenDependencies = fullyExportMavenDependencies;
}
@Option(longName="no-osgi")
@Description("Indicates that the generated car file should not contain OSGi module declarations.")
public void setNoOsgi(boolean noOsgi) {
this.noOsgi = noOsgi;
}
@OptionArgument(longName="osgi-provided-bundles", argumentName="modules")
@Description("Comma-separated list of module names. "
+ "The listed modules are expected to be OSGI bundles provided by the framework, "
+ "and will be omitted from the generated MANIFEST 'Required-Bundle' OSGI header.")
public void setOsgiProvidedBundles(String osgiProvidedBundles) {
this.osgiProvidedBundles = osgiProvidedBundles;
}
@Option(longName="generate-module-info")
@Description("Generate Java 9 (Jigsaw) `module-info.class` module descriptor in the generated Ceylon archive.")
public void setJigsaw(boolean jigsaw) {
this.jigsaw = jigsaw;
}
@Option(longName="no-pom")
@Description("Indicates that the generated car file should not contain Maven POM module declarations.")
public void setNoPom(boolean noPom) {
this.noPom = noPom;
}
@Option(longName="pack200")
@Description("Try to make the generated car file smaller by repacking it using `pack200`.")
public void setPack200(boolean pack200) {
this.pack200 = pack200;
}
@OptionArgument(shortName='s', longName="src", argumentName="dirs")
@ParsedBy(StandardArgumentParsers.PathArgumentParser.class)
@Description("Path to directory containing source files. " +
"Can be specified multiple times; you can also specify several " +
"paths separated by your operating system's `PATH` separator." +
" (default: `./source`)")
public void setSrc(List source) {
this.sources = source;
}
@OptionArgument(longName="source", argumentName="dirs")
@ParsedBy(StandardArgumentParsers.PathArgumentParser.class)
@Description("An alias for `--src`" +
" (default: `./source`)")
public void setSource(List source) {
setSrc(source);
}
@OptionArgument(shortName='r', longName="resource", argumentName="dirs")
@ParsedBy(StandardArgumentParsers.PathArgumentParser.class)
@Description("Path to directory containing resource files. " +
"Can be specified multiple times; you can also specify several " +
"paths separated by your operating system's `PATH` separator." +
" (default: `./resource`)")
public void setResource(List resource) {
this.resources = resource;
}
@OptionArgument(shortName='R', argumentName="folder-name")
@Description("Sets the special resource folder name whose files will " +
"end up in the root of the resulting module CAR file (default: ROOT).")
public void setResourceRoot(String resourceRoot) {
this.resourceRoot = resourceRoot;
}
@Hidden
@Option(longName="continue-on-errors")
@Description("Set to continue compiling even when errors are found.")
public void setContinueOnErrors(boolean continueOnErrors) {
this.continueOnErrors = continueOnErrors;
}
@Option(longName="progress")
@Description("Print progress information.")
public void setProgress(boolean progress) {
this.progress = progress;
}
@OptionArgument(shortName='E', argumentName="encoding")
@Description("Sets the encoding used for reading source files" +
"(default: platform-specific).")
public void setEncoding(String encoding) {
this.encoding = encoding;
}
@Option
@OptionArgument(argumentName = "flags")
@Description("Determines if and how compilation of dependencies should be handled. " +
"Allowed flags include: `never`, `once`, `force`, `check`.")
public void setIncludeDependencies(String includeDependencies) {
this.includeDependencies = includeDependencies;
}
@Option(longName="incremental")
@Description("Enables incremental compilation.")
public void setIncremental(boolean incremental) {
this.incremental = incremental;
}
@Argument(argumentName="moduleOrFile", multiplicity="*")
public void setModule(List moduleOrFile) {
this.modulesOrFiles = moduleOrFile;
}
@Option(shortName='d')
@OptionArgument(argumentName = "flags")
@Description("Produce verbose output. " +
"If no `flags` are given then be verbose about everything, " +
"otherwise just be verbose about the flags which are present. " +
"Allowed flags include: `all`, `loader`, `ast`, `code`, `cmr`, `benchmark`.")
public void setVerbose(String verbose) {
super.setVerbose(verbose);
}
protected Set getVerboseCategories(String... morecats) {
return super.getVerboseCategories("ast", "code", "cmr", "benchmark");
}
@OptionArgument(argumentName="option")
@Description("Passes an option to the underlying java compiler.")
public void setJavac(List javac) {
this.javac = javac;
}
@Option(shortName='W')
@OptionArgument(argumentName = "warnings")
@Description("Suppress the reporting of the given warnings. " +
"If no `warnings` are given then suppresss the reporting of all warnings, " +
"otherwise just suppresss those which are present. " +
"Allowed flags include: " +
"`filenameNonAscii`, `filenameCaselessCollision`, `deprecation`, "+
"`compilerAnnotation`, `doclink`, `expressionTypeNothing`, "+
"`unusedDeclaration`, `unusedImport`, `ceylonNamespace`, "+
"`javaNamespace`, `suppressedAlready`, `suppressesNothing`, "+
"`unknownWarning`, `ambiguousAnnotation`, `similarModule`, "+
"`importsOtherJdk`, `javaAnnotationElement`.")
public void setSuppressWarning(EnumSet warnings) {
this.suppressWarnings = warnings;
}
@OptionArgument(shortName='t', argumentName="version")
@Description("The JVM that generated .class files should target. Use `7` to target Java 7 JVMs "
+ "or `8` to target Java 8 JVMs.")
public void setTarget(Long version) {
validateWithJavac(com.redhat.ceylon.langtools.tools.javac.main.Option.TARGET, "-target", version.toString());
validateWithJavac(com.redhat.ceylon.langtools.tools.javac.main.Option.SOURCE, "-source", version.toString());
this.targetVersion = version;
}
@Option
@Description("Enable \"EE mode\" globally for all declarations in the compilation")
public void setEe(boolean ee) {
this.ee = ee;
}
@OptionArgument
@Description("Override the default module imports which trigger \"EE mode\" "
+ "with the given module imports."
+ "When a module *directly* imports any of the listed modules EE mode "
+ "will be enabled for all declarations in the module."
+ "For example if this option includes the value `javax.javaeeapi` "
+ "or `maven:\"javax.javaee-api\"` then EE mode would be "
+ "enabled for any declaration in any module which had that *direct* "
+ "module import.")
public void setEeImport(List eeImports) {
this.eeImport = eeImports;
}
@OptionArgument
@Description("Override the default annotation types which trigger \"EE mode\" "
+ "with the given fully-qualified Java annotation type name."
+ "When a declaration is annotated with any of the listed annotations EE "
+ "module will be enabled for the top-level declaration containing "
+ "that annotated declaration. "
+ "For example if this option includes the value `javax.inject.Inject` "
+ "then EE mode would be enabled for any class with an attribute annotated"
+ "with `javax.inject::inject`.")
public void setEeAnnotation(List eeAnnotations) {
this.eeAnnotation = eeAnnotations;
}
private List arguments;
private Main compiler;
@Override
protected List getSourceDirs() {
return sources;
}
@Override
protected List getResourceDirs() {
return resources;
}
private void validateWithJavac(com.redhat.ceylon.langtools.tools.javac.main.Option option, String argument, String value) {
helper.lastError = null;
if (option.hasArg()) {
if (option.process(helper, option.text, value)
|| helper.lastError != null) {
throw new IllegalArgumentException(helper.lastError);
}
} else {
if (option.process(helper, argument)
|| helper.lastError != null) {
throw new IllegalArgumentException(helper.lastError);
}
}
}
@Override
public void initialize(CeylonTool mainTool) throws Exception {
super.initialize(mainTool);
compiler = new Main("ceylon compile");
helper.options.clear();
Options.instance(new Context());
includeDependencies = processCompileFlags(includeDependencies, DefaultToolOptions.getCompilerIncludeDependencies());
if (modulesOrFiles.isEmpty() &&
!javac.contains("-help") &&
!javac.contains("-X") &&
!javac.contains("-version")) {
throw new IllegalStateException("Argument moduleOrFile should appear at least 1 time(s)");
}
arguments = new ArrayList<>();
if (cwd != null) {
arguments.add("-cwd");
arguments.add(cwd.getPath());
validateWithJavac(com.redhat.ceylon.langtools.tools.javac.main.Option.CEYLONCWD, "-cwd", cwd.getPath());
}
if(jdkProvider != null){
arguments.add("-jdk-provider");
arguments.add(jdkProvider.toString());
}
if(aptModules != null){
for(ModuleSpec mod : aptModules){
arguments.add("-apt");
arguments.add(mod.toString());
}
}
for (File source : applyCwd(this.sources)) {
arguments.add("-src");
arguments.add(source.getPath());
validateWithJavac(com.redhat.ceylon.langtools.tools.javac.main.Option.CEYLONSOURCEPATH, "-sourcepath", source.getPath());
}
for (File resource : applyCwd(this.resources)) {
arguments.add("-res");
arguments.add(resource.getPath());
}
if (resourceRoot != null) {
arguments.add("-resroot");
arguments.add(resourceRoot);
}
if (continueOnErrors) {
arguments.add("-continue");
}
if (progress) {
arguments.add("-progress");
}
if (offline) {
arguments.add("-offline");
}
if (timeout != -1) {
arguments.add("-timeout");
arguments.add(String.valueOf(timeout));
}
if (flatClasspath) {
arguments.add("-flat-classpath");
}
if (autoExportMavenDependencies) {
arguments.add("-auto-export-maven-dependencies");
}
if (fullyExportMavenDependencies) {
arguments.add("-fully-export-maven-dependencies");
}
if (overrides != null) {
arguments.add("-overrides");
if (overrides.startsWith("classpath:")) {
arguments.add(overrides);
} else {
arguments.add(applyCwd(new File(overrides)).getPath());
}
}
if (jigsaw) {
arguments.add("-module-info");
}
if (noOsgi) {
arguments.add("-noosgi");
}
if (osgiProvidedBundles != null
&& ! osgiProvidedBundles.isEmpty()) {
arguments.add("-osgi-provided-bundles");
arguments.add(osgiProvidedBundles);
}
if (noPom) {
arguments.add("-nopom");
}
if (pack200) {
arguments.add("-pack200");
}
if (verbose != null) {
if (verbose.isEmpty()) {
arguments.add("-verbose");
} else {
arguments.add("-verbose:" + verbose);
}
}
if (out != null) {
arguments.add("-out");
arguments.add(out);
}
if (user != null) {
arguments.add("-user");
arguments.add(user);
}
if (pass != null) {
arguments.add("-pass");
arguments.add(pass);
}
String fileEncoding = encoding;
if (fileEncoding == null) {
fileEncoding = DefaultToolOptions.getDefaultEncoding();
}
if (fileEncoding != null) {
try {
Charset.forName(fileEncoding);
} catch (IllegalCharsetNameException|UnsupportedCharsetException e) {
throw new IllegalArgumentException("Unsupported encoding: "+ fileEncoding);
}
arguments.add(com.redhat.ceylon.langtools.tools.javac.main.Option.ENCODING.text);
arguments.add(fileEncoding);
}
if (systemRepo != null) {
arguments.add("-sysrep");
arguments.add(systemRepo);
}
if (cacheRepo != null) {
arguments.add("-cacherep");
arguments.add(cacheRepo);
}
if (noDefRepos) {
arguments.add("-nodefreps");
}
if (repos != null) {
for (URI uri : this.repos) {
arguments.add("-rep");
arguments.add(uri.toString());
}
}
if (suppressWarnings != null) {
arguments.add("-suppress-warnings");
arguments.add(EnumUtil.enumsToString(suppressWarnings));
}
if (targetVersion != null) {
arguments.add("-source");
arguments.add(targetVersion.toString());
arguments.add("-target");
arguments.add(targetVersion.toString());
}
if (ee) {
arguments.add("-ee");
}
if (eeImport != null) {
for (String eeImport: this.eeImport) {
arguments.add("-ee-import");
arguments.add(eeImport);
}
}
if (eeAnnotation != null) {
for (String eeAnnotation: this.eeAnnotation) {
arguments.add("-ee-annotation");
arguments.add(eeAnnotation);
}
}
addJavacArguments(arguments, javac);
List srcs = applyCwd(this.sources);
Collection expandedModulesOrFiles = ModuleWildcardsHelper.expandWildcards(srcs , this.modulesOrFiles, Backend.Java);
expandedModulesOrFiles = normalizeFileNames(expandedModulesOrFiles);
if (expandedModulesOrFiles.isEmpty()) {
String msg = CeylonCompileMessages.msg("error.no.sources");
if (ModuleWildcardsHelper.onlyGlobArgs(this.modulesOrFiles)) {
throw new NonFatalToolMessage(msg);
} else {
throw new ToolUsageError(msg);
}
}
for (String moduleOrFile : expandedModulesOrFiles) {
if (!com.redhat.ceylon.langtools.tools.javac.main.Option.SOURCEFILE.matches(moduleOrFile)) {
throw new IllegalArgumentException(CeylonCompileMessages.msg("argument.error", moduleOrFile));
}
validateWithJavac(com.redhat.ceylon.langtools.tools.javac.main.Option.SOURCEFILE, moduleOrFile, moduleOrFile);
}
// We validate that all source arguments are correct
SourceArgumentsResolver sar = new SourceArgumentsResolver(this.sources, this.resources, Constants.CEYLON_SUFFIX, Constants.JAVA_SUFFIX);
sar.cwd(cwd).parse(expandedModulesOrFiles);
if (includeDependencies != null && !COMPILE_NEVER.equals(includeDependencies)) {
// Determine any dependencies that might need compiling as well
SourceDependencyResolver sdr = new SourceDependencyResolver(getModuleVersionReader(), this.sources, Backends.JAVA);
if (sdr.traverseDependencies(sar.getSourceFiles())) {
for (ModuleVersionDetails mvd : sdr.getAdditionalModules()) {
if (COMPILE_FORCE.equals(includeDependencies)
|| (COMPILE_CHECK.equals(includeDependencies) && shouldRecompile(getOfflineRepositoryManager(), mvd.getModule(), mvd.getVersion(), ModuleQuery.Type.JVM, true))
|| (COMPILE_ONCE.equals(includeDependencies) && shouldRecompile(getOfflineRepositoryManager(), mvd.getModule(), mvd.getVersion(), ModuleQuery.Type.JVM, false))) {
expandedModulesOrFiles.add(mvd.getModule());
if (incremental) {
sar.parse(expandedModulesOrFiles);
}
}
}
}
}
if (incremental) {
for (String module : sar.getModules()) {
// Determine module version from source
ModuleVersionDetails mvd = getModuleVersionReader().fromSource(module);
if (mvd != null) {
// Find the module's CAR file
File carFile = getModuleArtifact(getOfflineRepositoryManager(), mvd.getModule(), mvd.getVersion(), ModuleQuery.Type.JVM);
if (carFile != null) {
// Check if it has META-INF/errors.txt
Properties errors = getMetaInfErrors(carFile);
if (errors != null && !errors.isEmpty()) {
// If the module has errors we skip handling of
// --incremental on it and go to the next one
// TODO handle this incrementally
continue;
}
// Check if it has META-INF/hashes.txt
Properties oldHashes = getMetaInfHashes(carFile);
if (oldHashes != null) {
// Get the hashes for the new files
List files = sar.getFilesByModule().get(module);
Properties newHashes = getFileHashes(module, files);
// Compare the two and make list of changed files
Collection changedFiles = determineChangedFiles(module, oldHashes, newHashes, carFile);
if (changedFiles == null) {
// This shouldn't happen, but if it does we just skip any
// special treatment and compile this module normally
} else if (changedFiles.isEmpty()) {
// No files were changed, we shouldn't compile the module
expandedModulesOrFiles.remove(module);
// And we remove its files too if any were mentioned
Collection remove = filesToStrings(module, files);
expandedModulesOrFiles.removeAll(remove);
} else {
if (expandedModulesOrFiles.contains(module)) {
// The module itself was mentioned on the command line
if (changedFiles.size() < files.size()) {
// There were fewer changed files than the total number
// So we remove the module
expandedModulesOrFiles.remove(module);
// And we remove its files too if any were mentioned
Collection remove = filesToStrings(module, files);
expandedModulesOrFiles.removeAll(remove);
// And then we add only those files that were changed
expandedModulesOrFiles.addAll(changedFiles);
}
} else {
// Separate source files were mentioned on the command line
// So we remove the unchanged files
Collection unchanged = filesToStrings(module, files);
unchanged.removeAll(changedFiles);
expandedModulesOrFiles.removeAll(unchanged);
}
}
}
}
}
}
if (expandedModulesOrFiles.isEmpty()) {
String msg = CeylonCompileMessages.msg("error.no.need");
throw new NonFatalToolMessage(msg);
}
}
arguments.addAll(expandedModulesOrFiles);
if (verbose != null) {
System.out.println(arguments);
System.out.flush();
}
}
private Collection normalizeFileNames(Collection modulesOrFiles) {
Set result = new LinkedHashSet();
for (String mof : modulesOrFiles) {
if (mof.contains("/") || mof.contains("\\")) {
// It's a file
String path = FileUtil.relativeFile(sources, mof);
File norm = FileUtil.applyPath(allDirs(), path);
if (norm != null) {
result.add(norm.getPath());
} else {
result.add(mof);
}
} else {
// It's a module
result.add(mof);
}
}
return result;
}
private Properties getFileHashes(String moduleName, List files) {
Properties hashes = new Properties();
for (File f : files) {
String name = FileUtil.relativeFile(allDirs(), f.getPath());
name = handleResourceRoot(moduleName, name);
hashes.put(name, ShaSigner.sha1(f, null));
}
return hashes;
}
private String handleResourceRoot(String moduleName, String relFileName) {
String rrp = resourceRoot.isEmpty() ? "" : ModuleUtil.moduleToPath(moduleName).getPath() + "/" + resourceRoot + "/";
if (!rrp.isEmpty() && relFileName.startsWith(rrp)) {
relFileName = relFileName.substring(rrp.length());
}
return relFileName;
}
private Collection determineChangedFiles(String moduleName, Properties oldHashes, Properties newHashes, File carFile) {
HashMap diff = new HashMap();
// First we add all the new files and hashes to our diff
for (String name : newHashes.stringPropertyNames()) {
diff.put(name, newHashes.getProperty(name));
}
// Then we remove those that can be found in the old hashes
for (String name : oldHashes.stringPropertyNames()) {
String hash = oldHashes.getProperty(name);
if (hash.equals(diff.get(name))) {
diff.remove(name);
}
}
// And finally we create a list of applied paths, but for the
// ones that were not among the old hashes (meaning they are
// either new or they are source files that didn't result in
// any code being generated) we first check the file time stamp
// and don't add them if they are older than the CAR file
List result = new ArrayList(diff.size());
for (String name : diff.keySet()) {
File full = FileUtil.applyPath(allDirs(), name);
if (full == null) {
// This really shouldn't happen
return null;
}
try {
String hash = oldHashes.getProperty(name);
if (hash != null
|| isModuleArtifactOutOfDate(carFile, moduleName, Type.JVM)) {
result.add(full.getPath());
}
} catch (IOException e) {
// Ignore
}
}
return result;
}
private Collection filesToStrings(String moduleName, Collection files) {
List result = new ArrayList(files.size());
for (File f : files) {
String norm = handleResourceRoot(moduleName, f.getPath());
result.add(norm);
}
return result;
}
/**
* Run the compilation
* @throws IOException
* @throws CompilerErrorException If the source code had errors
* @throws SystemErrorException If there was a system error
* @throws CompilerBugException If a bug in the compiler was detected.
*/
@Override
public void run() throws IOException {
Result result = compiler.compile(arguments.toArray(new String[arguments.size()]));
handleExitCode(result.exitCode, compiler.exitState);
}
private void handleExitCode(
int javacExitCode,
Main.ExitState exitState) {
if (exitState == null) {
throw new IllegalStateException("Missing ExitState, " + javacExitCode);
}
CeylonState ceylonState = exitState.ceylonState;
switch (ceylonState) {
case OK:
break;
case ERROR:
throw new CompilerErrorException(exitState.errorCount);
case SYS:
throw new SystemErrorException(exitState.abortingException);
case BUG:
throw new CompilerBugException(exitState);
default:
throw new IllegalStateException("Unexpected CeylonState " + ceylonState);
}
}
public static void addJavacArguments(List arguments, List javac) {
Helper helper = new Helper();
for (String argument : javac) {
helper.lastError = null;
String value = null;
int index = argument.indexOf('=');
if (index != -1 &&
!com.redhat.ceylon.langtools.tools.javac.main.Option.A.matches(argument)) {
value = index < argument.length() ? argument.substring(index+1) : "";
argument = argument.substring(0, index);
}
com.redhat.ceylon.langtools.tools.javac.main.Option javacOpt = getJavacOpt(argument);
if (javacOpt == null) {
throw new IllegalArgumentException(CeylonCompileMessages.msg("option.error.javac", argument));
}
if (value != null) {
if (!javacOpt.hasArg()) {
throw new IllegalArgumentException(CeylonCompileMessages.msg("option.error.syntax.javac", argument, "Unexpected argument given"));
}
if (!javacOpt.matches(argument)) {
throw new IllegalArgumentException(CeylonCompileMessages.msg("option.error.javac", argument));
}
if (javacOpt.process(helper, argument, value)) {
throw new IllegalArgumentException(CeylonCompileMessages.msg("option.error.syntax.javac", argument, helper.lastError));
}
} else {
if (javacOpt.hasArg()) {
throw new IllegalArgumentException(CeylonCompileMessages.msg("option.error.syntax.javac", argument, "Missing expected argument"));
}
if (!javacOpt.matches(argument)) {
throw new IllegalArgumentException(CeylonCompileMessages.msg("option.error.javac", argument));
}
if (javacOpt.process(helper, argument)) {
throw new IllegalArgumentException(CeylonCompileMessages.msg("option.error.syntax.javac", argument, helper.lastError));
}
}
arguments.add(argument);
if (value != null) {
arguments.add(value);
}
}
}
private static com.redhat.ceylon.langtools.tools.javac.main.Option getJavacOpt(String optionName) {
for (com.redhat.ceylon.langtools.tools.javac.main.Option o : com.redhat.ceylon.langtools.tools.javac.main.Option.getJavaCompilerOptions()) {
if (o.matches(optionName)) {
return o;
}
}
return null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy