blob: 8b64bf1eca0e46e6039ba05c314d6ed547e2dc9c [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.jack;
import com.google.common.base.Joiner;
import com.android.jack.backend.dex.DexFileWriter;
import com.android.jack.backend.dex.MultiDexLegacy;
import com.android.jack.backend.dex.compatibility.AndroidCompatibilityChecker;
import com.android.jack.backend.dex.rop.CodeItemBuilder;
import com.android.jack.config.id.Arzon;
import com.android.jack.config.id.Carnac;
import com.android.jack.config.id.JavaVersionPropertyId;
import com.android.jack.incremental.InputFilter;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.library.DumpInLibrary;
import com.android.jack.library.InputLibrary;
import com.android.jack.library.LibraryPathPropertyId;
import com.android.jack.library.PrebuiltCompatibility;
import com.android.jack.meta.MetaImporter;
import com.android.jack.plugin.JackPluginJarCodec;
import com.android.jack.plugin.NotJackPluginException;
import com.android.jack.plugin.PluginManager;
import com.android.jack.plugin.PluginNotFoundException;
import com.android.jack.reporting.Reportable;
import com.android.jack.reporting.Reportable.ProblemLevel;
import com.android.jack.reporting.Reporter;
import com.android.jack.reporting.Reporter.Severity;
import com.android.jack.resource.ResourceImporter;
import com.android.jack.shrob.obfuscation.MappingPrinter;
import com.android.jack.shrob.obfuscation.NameProviderFactory;
import com.android.jack.shrob.obfuscation.Renamer;
import com.android.jack.shrob.obfuscation.SourceFileRenamer;
import com.android.jack.shrob.obfuscation.annotation.AnnotationRemover;
import com.android.jack.shrob.obfuscation.annotation.ParameterAnnotationRemover;
import com.android.jack.shrob.seed.SeedPrinter;
import com.android.jack.shrob.spec.Flags;
import com.android.jack.transformations.lambda.LambdaGroupingScope;
import com.android.jack.transformations.renamepackage.PackageRenamer;
import com.android.jack.util.AndroidApiLevel;
import com.android.jack.util.AndroidApiLevelCodec;
import com.android.jack.util.ClassNameCodec;
import com.android.jack.util.args4j.JackEnumOptionHandler;
import com.android.jack.util.filter.Filter;
import com.android.sched.reflections.ReflectionFactory;
import com.android.sched.util.RunnableHooks;
import com.android.sched.util.SubReleaseKind;
import com.android.sched.util.codec.CaseInsensitiveDirectFSCodec;
import com.android.sched.util.codec.CodecContext;
import com.android.sched.util.codec.DirectDirOutputVFSCodec;
import com.android.sched.util.codec.DirectoryCodec;
import com.android.sched.util.codec.EnumName;
import com.android.sched.util.codec.InputFileOrDirectoryCodec;
import com.android.sched.util.codec.ListCodec;
import com.android.sched.util.codec.PairCodec;
import com.android.sched.util.codec.PairCodec.Pair;
import com.android.sched.util.codec.PairListToMapCodecConverter;
import com.android.sched.util.codec.ParsingException;
import com.android.sched.util.codec.ReaderFileOrDirectoryCodec;
import com.android.sched.util.codec.StringValueCodec;
import com.android.sched.util.codec.VariableName;
import com.android.sched.util.codec.ZipFSCodec;
import com.android.sched.util.codec.ZipOutputVFSCodec;
import com.android.sched.util.config.Config;
import com.android.sched.util.config.ConfigurationException;
import com.android.sched.util.config.GatherConfigBuilder;
import com.android.sched.util.config.HasKeyId;
import com.android.sched.util.config.PropertyIdException;
import com.android.sched.util.config.category.Private;
import com.android.sched.util.config.id.BooleanPropertyId;
import com.android.sched.util.config.id.EnumPropertyId;
import com.android.sched.util.config.id.ImplementationPropertyId;
import com.android.sched.util.config.id.ListPropertyId;
import com.android.sched.util.config.id.MessageDigestPropertyId;
import com.android.sched.util.config.id.ObjectId;
import com.android.sched.util.config.id.PropertyId;
import com.android.sched.util.config.id.ReflectFactoryPropertyId;
import com.android.sched.util.file.CannotChangePermissionException;
import com.android.sched.util.file.CannotCreateFileException;
import com.android.sched.util.file.Directory;
import com.android.sched.util.file.FileAlreadyExistsException;
import com.android.sched.util.file.FileOrDirectory;
import com.android.sched.util.file.FileOrDirectory.ChangePermission;
import com.android.sched.util.file.FileOrDirectory.Existence;
import com.android.sched.util.file.FileOrDirectory.Permission;
import com.android.sched.util.file.FileUtils;
import com.android.sched.util.file.Files;
import com.android.sched.util.file.InputJarFile;
import com.android.sched.util.file.NoSuchFileException;
import com.android.sched.util.file.NotDirectoryException;
import com.android.sched.util.file.OutputZipFile.Compression;
import com.android.sched.util.file.WriterFile;
import com.android.sched.util.file.WrongPermissionException;
import com.android.sched.util.location.FileLocation;
import com.android.sched.util.location.NoLocation;
import com.android.sched.util.location.StringLocation;
import com.android.sched.util.log.LoggerFactory;
import com.android.sched.util.log.TracerFactory;
import com.android.sched.util.log.tracer.StatsTracerFtl;
import com.android.sched.vfs.Container;
import com.android.sched.vfs.OutputVFSPropertyId;
import com.android.sched.vfs.VFSPropertyId;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.spi.ExplicitBooleanOptionHandler;
import org.kohsuke.args4j.spi.MapOptionHandler;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Jack command line options Bean
*/
@HasKeyId
public class Options {
@Nonnull
private static final Logger logger = LoggerFactory.getLogger();
private static class DeprecatedVerbosity implements Reportable {
@Nonnull
private final VerbosityLevel verbosity;
private DeprecatedVerbosity(@Nonnull VerbosityLevel verbosity) {
this.verbosity = verbosity;
}
@Override
@Nonnull
public String getMessage() {
return "Verbosity level '" + verbosity.name().toLowerCase() + "' is deprecated";
}
@Override
@Nonnull
public ProblemLevel getDefaultProblemLevel() {
return ProblemLevel.WARNING;
}
}
/**
* Assertion policies
*/
@VariableName("policy")
public enum AssertionPolicy {
@EnumName(name = "always", description = "always check assert statements")
ALWAYS,
@EnumName(name = "never", description = "remove assert statements")
NEVER,
@EnumName(name = "runtime", description = "check according to runtime configuration")
RUNTIME;
}
@Nonnull
public static final EnumPropertyId<AssertionPolicy> ASSERTION_POLICY =
EnumPropertyId.create(
"jack.assert.policy",
"Assert statement policy",
AssertionPolicy.class)
.addDefaultValue(AssertionPolicy.RUNTIME)
.ignoreCase()
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class)
.addCategory(Carnac.class);
@Nonnull
public static final BooleanPropertyId INCREMENTAL_MODE = BooleanPropertyId
.create("jack.incremental", "Enable incremental mode")
.addDefaultValue(Boolean.FALSE).addCategory(DumpInLibrary.class);
@Nonnull
public static final ReflectFactoryPropertyId<InputFilter> INPUT_FILTER = ReflectFactoryPropertyId
.create("jack.input.filter", "Inputs filter", InputFilter.class)
.addDefaultValue("no-filter").addArgType(Options.class);
@Nonnull
public static final JavaVersionPropertyId JAVA_SOURCE_VERSION =
JavaVersionPropertyId.create("jack.java.source.version", "Java source version")
.addDefaultValue("1.7").addCategory(Arzon.class).addCategory(DumpInLibrary.class);
@Nonnull
public static final BooleanPropertyId LAMBDA_TO_ANONYMOUS_CONVERTER = BooleanPropertyId
.create("jack.lambda.anonymous", "Enable lambda support with an anonymous class")
.addDefaultValue(Boolean.TRUE)
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class);
@Nonnull
public static final EnumPropertyId<LambdaGroupingScope> LAMBDA_GROUPING_SCOPE = EnumPropertyId
.create(
"jack.lambda.grouping-scope", "Defines the scope for lambda grouping",
LambdaGroupingScope.class)
.ignoreCase()
.addDefaultValue(LambdaGroupingScope.NONE)
.requiredIf(LAMBDA_TO_ANONYMOUS_CONVERTER.getValue().isTrue())
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class)
.addCategory(Private.class);
@Nonnull
public static final MessageDigestPropertyId LAMBDA_NAME_DIGEST_ALGO = MessageDigestPropertyId
.create("jack.lambda.name.digest.algo", "Digest algorithm use for lambda class name")
.requiredIf(LAMBDA_TO_ANONYMOUS_CONVERTER.getValue().isTrue()).addDefaultValue("SHA");
@Nonnull
public static final BooleanPropertyId LAMBDA_MERGE_INTERFACES = BooleanPropertyId
.create("jack.lambda.merge-interfaces",
"Allows merging functional interfaces")
.addDefaultValue(Boolean.FALSE)
.requiredIf(Options.LAMBDA_TO_ANONYMOUS_CONVERTER.getValue().isTrue())
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class)
.addCategory(Private.class);
@Nonnull
public static final BooleanPropertyId LAMBDA_SIMPLIFY_STATELESS = BooleanPropertyId
.create("jack.lambda.simplify-stateless",
"Simplifies stateless lambda to use single-instance implementation")
.addDefaultValue(Boolean.FALSE)
.requiredIf(Options.LAMBDA_TO_ANONYMOUS_CONVERTER.getValue().isTrue())
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class)
.addCategory(Private.class);
@Nonnull
public static final BooleanPropertyId GENERATE_JACK_LIBRARY = BooleanPropertyId.create(
"jack.library", "Generate jack library").addDefaultValue(Boolean.FALSE);
@Nonnull
public static final BooleanPropertyId GENERATE_DEX_FILE =
BooleanPropertyId.create("jack.dex", "Generate dex file").addDefaultValue(Boolean.FALSE)
.addCategory(DumpInLibrary.class);
/**
* property used to specify the kind of switch enum optimization that is enabled. See(@link
* SwitchEnumOptStrategy)
*/
@Nonnull
public static final EnumPropertyId<SwitchEnumOptStrategy> OPTIMIZED_ENUM_SWITCH =
EnumPropertyId.create(
"jack.optimization.enum.switch", "Optimize enum switch", SwitchEnumOptStrategy.class)
.addDefaultValue(SwitchEnumOptStrategy.NEVER)
.ignoreCase()
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class);
@Nonnull
public static final BooleanPropertyId GENERATE_DEX_IN_LIBRARY = BooleanPropertyId
.create("jack.library.dex", "Generate dex files in library").addDefaultValue(Boolean.TRUE)
.requiredIf(GENERATE_JACK_LIBRARY.getValue().isTrue());
@Nonnull
public static final BooleanPropertyId USE_PREBUILT_FROM_LIBRARY =
BooleanPropertyId.create("jack.library.prebuilt.use", "Use prebuilt files from library")
.addDefaultValue(Boolean.TRUE)
.requiredIf(
GENERATE_DEX_FILE.getValue().isTrue().or(GENERATE_JACK_LIBRARY.getValue().isTrue()))
.addCategory(DumpInLibrary.class);
@Nonnull
public static final BooleanPropertyId GENERATE_JAYCE_IN_LIBRARY = BooleanPropertyId
.create("jack.library.jayce", "Generate Jayce files in library")
.addDefaultValue(Boolean.FALSE).addCategory(Private.class)
.requiredIf(GENERATE_JACK_LIBRARY.getValue().isTrue());
@Nonnull
public static final BooleanPropertyId GENERATE_DEPENDENCIES_IN_LIBRARY = BooleanPropertyId
.create("jack.library.dependencies", "Generate Dependency files in library")
.addDefaultValue(Boolean.FALSE).addCategory(Private.class)
.requiredIf(GENERATE_JACK_LIBRARY.getValue().isTrue());
@Nonnull
public static final
BooleanPropertyId GENERATE_LIBRARY_FROM_INCREMENTAL_FOLDER = BooleanPropertyId.create(
"jack.library.from-incremental-folder",
"Generate a jack library from the incremental folder").addDefaultValue(Boolean.FALSE)
.addCategory(Private.class);
@Nonnull
public static final EnumPropertyId<Container> DEX_OUTPUT_CONTAINER_TYPE =
EnumPropertyId.create("jack.dex.output.container", "Output container type", Container.class)
.ignoreCase().requiredIf(GENERATE_DEX_FILE.getValue().isTrue());
@Nonnull
public static final EnumPropertyId<Container> LIBRARY_OUTPUT_CONTAINER_TYPE = EnumPropertyId
.create("jack.library.output.container", "Library output container type", Container.class)
.ignoreCase().requiredIf(GENERATE_JACK_LIBRARY.getValue().isTrue());
@Nonnull
public static final VFSPropertyId LIBRARY_OUTPUT_ZIP = VFSPropertyId
.create("jack.library.output.zip", "Output zip archive for library",
new ZipFSCodec(Existence.MAY_EXIST, Compression.UNCOMPRESSED).setInfoString("output-lib"))
.requiredIf(GENERATE_JACK_LIBRARY.getValue().isTrue()
.and(LIBRARY_OUTPUT_CONTAINER_TYPE.is(Container.ZIP))
.or(GENERATE_LIBRARY_FROM_INCREMENTAL_FOLDER.getValue().isTrue()));
@Nonnull
public static final VFSPropertyId LIBRARY_OUTPUT_DIR =
VFSPropertyId.create(
"jack.library.output.dir",
"Output folder for library",
new CaseInsensitiveDirectFSCodec(Existence.MUST_EXIST).setInfoString("output-lib"))
.withoutAutoAction()
.requiredIf(
GENERATE_JACK_LIBRARY
.getValue()
.isTrue()
.and(LIBRARY_OUTPUT_CONTAINER_TYPE.is(Container.DIR)));
@Nonnull
public static final OutputVFSPropertyId DEX_OUTPUT_DIR = OutputVFSPropertyId.create(
"jack.dex.output.dir", "Output folder for dex",
new DirectDirOutputVFSCodec(Existence.MUST_EXIST).setInfoString("output-dex"))
.withoutAutoAction().requiredIf(
DEX_OUTPUT_CONTAINER_TYPE.is(Container.DIR));
@Nonnull
public static final OutputVFSPropertyId DEX_OUTPUT_ZIP = OutputVFSPropertyId.create(
"jack.dex.output.zip", "Output zip archive for dex",
new ZipOutputVFSCodec(Existence.MAY_EXIST).setInfoString("output-dex")).requiredIf(
DEX_OUTPUT_CONTAINER_TYPE.is(Container.ZIP));
@Nonnull
public static final LibraryPathPropertyId IMPORTED_LIBRARIES =
new LibraryPathPropertyId("jack.library.import", "Libraries to import", "imported-lib")
.withoutAutoAction()
.addDefaultValue(Collections.<InputLibrary>emptyList());
@Nonnull
public static final LibraryPathPropertyId CLASSPATH =
new LibraryPathPropertyId("jack.classpath", "Classpath", "classpath-lib")
.withoutAutoAction()
.addDefaultValue(Collections.<InputLibrary>emptyList());
@Nonnull
public static final BooleanPropertyId ENABLE_COMPILED_FILES_STATISTICS = BooleanPropertyId.create(
"jack.statistic.source", "Enable compiled files statistics").addDefaultValue(
Boolean.FALSE);
@Nonnull
public static final BooleanPropertyId ANNOTATION_PROCESSOR_ENABLED =
BooleanPropertyId.create(
"jack.annotation-processor", "Enable annotation processors")
.addDefaultValue(true).addCategory(DumpInLibrary.class);
@Option(name = "--version", usage = "display version")
private boolean version;
@Option(name = "--help", usage = "display help")
private boolean help;
@Option(name = "--help-properties", usage = "display properties list")
private boolean helpProperties;
@Option(name = "-D", metaVar = "<property>=<value>",
usage = "set value for the given property (repeatable)",
handler = MapOptionHandler.class)
@Nonnull
private final Map<String, String> properties = new HashMap<String, String>();
@Option(name = "-A", metaVar = "<option>=<value>",
usage = "set option for annotation processors (repeatable)",
handler = MapOptionHandler.class)
@CheckForNull
private Map<String, String> annotationProcessorOption;
@Nonnull
public static final PropertyId<Map<String, String>> ANNOTATION_PROCESSOR_OPTIONS = PropertyId
.create(
"jack.annotation-processor.options",
"Options for annotation processors",
new PairListToMapCodecConverter<String, String>(new ListCodec<Pair<String, String>>(
new PairCodec<String, String>(new StringValueCodec(
"an annotation processor option name", "option"), new StringValueCodec(
"an annotation processor option value", "value"))).setMin(0))).addDefaultValue(
Collections.<String, String>emptyMap());
private final File propertiesFile = null;
/**
* Jack verbosity level.
* Note: The implementation of {@link ProblemLevel} assumes that the ordinal values of
* {@link VerbosityLevel} are ordered from the highest severity to the lowest.
*/
@VariableName("level")
public enum VerbosityLevel {
@EnumName(name = "error")
ERROR("error"),
@EnumName(name = "warning")
WARNING("warning"),
@EnumName(name = "info")
INFO("info"),
@EnumName(name = "debug", hide = true)
@Deprecated DEBUG("debug"),
@EnumName(name = "trace", hide = true)
@Deprecated TRACE("trace");
@Nonnull
private final String id;
VerbosityLevel(@Nonnull String id) {
this.id = id;
}
public String getId() {
return id;
}
}
/**
* Types of switch enum optimization strategies.
* 1. feedback (set on by default)
* 2. always
* 3. never
*/
@VariableName("strategy")
public enum SwitchEnumOptStrategy {
// feedback-based optimization: this strategy will be enabled/disabled based on the
// compile time information collected, e.g., if it is detected that an enum is only
// used in one/few switch statements, it is useless to optimize it. Potentially enable
// this strategy will cost more compilation time, but save more dex code
@EnumName(name = "feedback")
FEEDBACK(),
// different from feedback-based optimization, always strategy doesn't collect compile-
// time information to guide switch enum optimization. It will always enable switch enum
// optimization no matter the enum is rarely/frequently used. Ideally this strategy will
// compile code quicker than feedback-based strategy does, but the generated dex may be
// larger than feedback strategy
@EnumName(name = "always")
ALWAYS(),
// this actually is not real strategy, but we still need it because switch enum
// optimization is disabled when incremental compilation is triggered
@EnumName(name = "never")
NEVER();
}
@Nonnull
public static final EnumPropertyId<VerbosityLevel> VERBOSITY_LEVEL = EnumPropertyId.create(
"jack.verbose.level", "Verbosity level", VerbosityLevel.class)
.addDefaultValue(VerbosityLevel.WARNING);
@Option(name = "--verbose", usage = "set verbosity (default: warning)",
handler = JackEnumOptionHandler.class)
private VerbosityLevel verbose = VerbosityLevel.WARNING;
/**
* Folder used for incremental data.
*/
@Option(name = "--incremental-folder", usage = "directory used for incremental data",
metaVar = "<DIRECTORY>")
private File incrementalFolder = null;
@Option(name = "--output-dex", usage = "output dex files and resources to the directory",
metaVar = "<DIRECTORY>")
private File out = null;
/**
* Output to this zip file.
*/
@Option(name = "--output-dex-zip", metaVar = "<FILE>")
private File outZip = null;
/**
* Output jack library to this folder.
*/
private File libraryOutDir = null;
@Option(name = "--output-jack", usage = "output jack library file", metaVar = "<FILE>")
private File libraryOutZip = null;
@Option(name = "--config-jarjar", usage = "use jarjar rules files (default: none)",
metaVar = "<FILE>")
private List<File> jarjarRulesFiles = new ArrayList<File>(0);
@Option(name = "--import", usage = "import the given file into the output (repeatable)",
metaVar = "<FILE>")
protected List<File> importedLibraries = new ArrayList<File>();
@Option(name = "--import-resource",
usage = "import the given directory into the output as resource files (repeatable)",
metaVar = "<DIRECTORY>")
private List<File> resImport = new ArrayList<File>();
@Option(name = "--import-meta",
usage = "import the given directory into the output as meta-files (repeatable)",
metaVar = "<DIRECTORY>")
private List<File> metaImport = new ArrayList<File>();
@Option(name = "--config-proguard",
usage = "use a proguard flags file (default: none) (repeatable)",
metaVar = "<FILE>")
protected List<File> proguardFlagsFiles = null;
/**
* Enable/disable compiler sanity checks.
*/
@Option(name = "--sanity-checks",
handler = ExplicitBooleanOptionHandler.class, metaVar = "[on | off]")
private boolean sanityChecks =
Jack.getVersion().getSubReleaseKind() == SubReleaseKind.ENGINEERING;
@Nonnull
public static final BooleanPropertyId SANITY_CHECKS =
BooleanPropertyId.create("jack.sanity-checks", "Compiler sanity checks")
.addCategory(DumpInLibrary.class)
.addDefaultValue(Jack.getVersion().getSubReleaseKind() == SubReleaseKind.ENGINEERING);
/**
* Enable tracer and output into this dir (.html).
*/
@Option(name = "--tracer-dir",
metaVar = "<DIRECTORY>")
private File tracerDir;
/*
* Annotation processor
*/
@Option(name = "--processorpath", usage = "annotation processor classpath", metaVar = "<PATH>")
@CheckForNull
private String processorPath;
@Option(name = "--processor", usage = "annotation processor class names",
metaVar = "<NAME>[,<NAME>...]")
@CheckForNull
private String processor;
@Nonnull
public static final BooleanPropertyId ANNOTATION_PROCESSOR_MANUAL =
BooleanPropertyId.create(
"jack.annotation-processor.manual", "run only specified annotation processors")
.addDefaultValue(false).addCategory(DumpInLibrary.class);
@Nonnull
public static final ListPropertyId<String> ANNOTATION_PROCESSOR_MANUAL_LIST =
new ListPropertyId<String>("jack.annotation-processor.manual.list",
"Annotation processor class names", new ClassNameCodec()).minElements(0)
.requiredIf(ANNOTATION_PROCESSOR_MANUAL.getValue().isTrue());
@Nonnull
public static final PropertyId<Directory> ANNOTATION_PROCESSOR_SOURCE_OUTPUT_DIR =
PropertyId.create(
"jack.annotation-processor.source.output",
"Output folder for sources generated by annotation processors",
new DirectoryCodec(Existence.MUST_EXIST, Permission.WRITE | Permission.READ));
@Nonnull
public static final PropertyId<Directory> ANNOTATION_PROCESSOR_CLASS_OUTPUT_DIR =
PropertyId.create(
"jack.annotation-processor.class.output",
"Output folder for classes generated by annotation processors",
new DirectoryCodec(Existence.MUST_EXIST, Permission.WRITE | Permission.READ));
@Nonnull
public static final BooleanPropertyId ANNOTATION_PROCESSOR_PATH =
BooleanPropertyId.create(
"jack.annotation-processor.path",
"Use annotation processor classpath for annotation processor loading")
.addDefaultValue(false);
@Nonnull
public static final ListPropertyId<FileOrDirectory> ANNOTATION_PROCESSOR_PATH_LIST =
new ListPropertyId<FileOrDirectory>("jack.annotation-processor.path.list",
"Annotation processor classpath", new InputFileOrDirectoryCodec()).on(File.pathSeparator)
.minElements(0).requiredIf(ANNOTATION_PROCESSOR_PATH.getValue().isTrue());
/*
*
*/
@Nonnull
public static final BooleanPropertyId USE_DEFAULT_LIBRARIES = BooleanPropertyId.create(
"jack.classpath.default-libraries", "Use default libraries as first classpath entries")
.addDefaultValue(Boolean.TRUE).addCategory(DumpInLibrary.class);
@Nonnull
public static final ListPropertyId<FileOrDirectory> SOURCE_PATH =
new ListPropertyId<FileOrDirectory>("jack.source.path",
"Source path", new InputFileOrDirectoryCodec()).on(File.pathSeparator)
.minElements(0).addDefaultValue(Collections.<FileOrDirectory>emptyList());
@Nonnull
@Option(name = "-cp", aliases = "--classpath", usage = "set classpath", metaVar = "<PATH>")
protected String classpath = "";
// This is a trick to document @<FILE>, but it has no real link to ecjArguments
@Argument(usage = "read command line from file", metaVar = "@<FILE>")
@CheckForNull
protected List<File> inputSources;
@Nonnull
public static final ListPropertyId<FileOrDirectory> SOURCES =
new ListPropertyId<FileOrDirectory>("jack.source", "Sources to compile",
new ReaderFileOrDirectoryCodec()).on(File.pathSeparator).minElements(0)
.addDefaultValue(Collections.<FileOrDirectory>emptyList());
@Nonnull
private final List<String> ecjExtraArguments = new ArrayList<String>();
@Option(name = "-g", usage = "emit debug infos")
private Boolean emitLocalDebugInfo;
/**
* Available mode for the multidex feature
*/
public enum MultiDexKind {
NONE,
NATIVE,
LEGACY
}
@Option(name = "--multi-dex",
usage = "whether to split code into multiple dex files (default: none)",
handler = JackEnumOptionHandler.class)
private MultiDexKind multiDexKind = MultiDexKind.NONE;
@Nonnull
public static final BooleanPropertyId OPTIMIZE_INNER_CLASSES_ACCESSORS = BooleanPropertyId.create(
"jack.optimization.inner-class.accessors",
"Avoid creating synthethic accessors for outer class private fields and methods")
.addDefaultValue(Boolean.FALSE).addCategory(DumpInLibrary.class);
@Nonnull
public static final BooleanPropertyId OPTIMIZE_TAIL_RECURSION =
BooleanPropertyId.create("jack.optimization.tail-recursion", "Optimize tail recursive calls")
.addDefaultValue(Boolean.FALSE)
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class);
@Nonnull
public static final BooleanPropertyId EMIT_LOCAL_DEBUG_INFO =
BooleanPropertyId.create(
"jack.dex.debug.vars", "Emit local variable debug info into generated dex")
.addDefaultValue(Boolean.FALSE)
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class);
@Nonnull
public static final BooleanPropertyId EMIT_LINE_NUMBER_DEBUG_INFO =
BooleanPropertyId.create(
"jack.dex.debug.lines", "Emit line number debug info into generated dex")
.addDefaultValue(Boolean.TRUE)
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class);
@Nonnull
public static final BooleanPropertyId EMIT_SOURCE_FILE_DEBUG_INFO =
BooleanPropertyId.create(
"jack.dex.debug.source", "Emit source file debug info into generated dex")
.addDefaultValue(Boolean.TRUE)
.addCategory(DumpInLibrary.class)
.addCategory(PrebuiltCompatibility.class);
@Nonnull
public static final PropertyId<AndroidApiLevel> ANDROID_MIN_API_LEVEL = PropertyId
.create("jack.android.min-api-level", "Minimum Android API level compatibility",
new AndroidApiLevelCodec())
.addDefaultValue(new AndroidApiLevel(AndroidApiLevel.ReleasedLevel.B))
.addCategory(DumpInLibrary.class)
.addCategory(Carnac.class)
.addCategory(new PrebuiltCompatibility() {
@Override
public boolean isCompatible(@Nonnull Config config, @Nonnull String valueFromLibrary)
throws ParsingException {
AndroidApiLevel levelFromLib = config.parseAs(valueFromLibrary, ANDROID_MIN_API_LEVEL);
AndroidApiLevel levelFromConf = config.get(ANDROID_MIN_API_LEVEL);
if (levelFromLib.isReleasedLevel() && levelFromConf.isReleasedLevel()) {
return levelFromLib.getReleasedLevel() <= levelFromConf.getReleasedLevel();
} else if (!levelFromLib.isReleasedLevel() && !levelFromConf.isReleasedLevel()) {
return levelFromLib.getProvisionalLevel() == levelFromConf.getProvisionalLevel();
} else {
return false;
}
}
});
@Nonnull
public static final BooleanPropertyId DROP_METHOD_BODY = BooleanPropertyId.create(
"jack.internal.dropmethodbody", "Drop method bodies when they are no longer useful")
.addDefaultValue(Boolean.TRUE);
@Nonnull
public static final BooleanPropertyId SHRINKING_ENABLED =
BooleanPropertyId.create("jack.shrob.shrink", "Enable shrinking feature")
.addDefaultValue(false).addCategory(DumpInLibrary.class);
@Nonnull
public static final BooleanPropertyId OBFUSCATION_ENABLED =
BooleanPropertyId.create("jack.shrob.obfuscate", "Enable obfuscation feature")
.addDefaultValue(false).addCategory(DumpInLibrary.class);
@CheckForNull
protected Flags flags = null;
@Nonnull
public static final ObjectId<Flags> FLAGS = new ObjectId<Flags>("jack.shrob.flags", Flags.class);
@Nonnull
public static final BooleanPropertyId USE_MIXED_CASE_CLASSNAME =
BooleanPropertyId
.create("jack.obfuscation.mixedcaseclassname",
"Use mixed case class name when obfuscating")
.addDefaultValue(Boolean.FALSE).addCategory(DumpInLibrary.class);
@Nonnull
public static final BooleanPropertyId EMIT_CLASS_FILES =
BooleanPropertyId.create("jack.class-file", "Generate class files")
.addDefaultValue(false);
@Nonnull
public static final PropertyId<Directory> EMIT_CLASS_FILES_FOLDER =
PropertyId.create(
"jack.class-file.output.dir",
"Output folder for class files",
new DirectoryCodec(Existence.MUST_EXIST, Permission.WRITE | Permission.READ))
.requiredIf(EMIT_CLASS_FILES.getValue().isTrue());
@SuppressWarnings("unchecked")
@Nonnull
public static final ImplementationPropertyId<Filter<JMethod>> METHOD_FILTER =
(ImplementationPropertyId<Filter<JMethod>>) (Object) ImplementationPropertyId.create(
"jack.internal.filter.method", "Define which filter will be used for methods",
Filter.class).addDefaultValue("all-methods");
@CheckForNull
private OutputStream reporterStream = null;
/*
* CodecContext
*/
@CheckForNull
private File workingDirectory = null;
@CheckForNull
private PrintStream standardError = null;
@CheckForNull
private PrintStream standardOutput = null;
public void setWorkingDirectory(@Nonnull File workingDirectory) {
this.workingDirectory = workingDirectory;
}
public void setStandardError(@Nonnull PrintStream standardError) {
this.standardError = standardError;
}
public void setStandardOutput(@Nonnull PrintStream standardOutput) {
this.standardOutput = standardOutput;
}
@CheckForNull
private Charset defaultCharset = null;
public void setDefaultCharset(@Nonnull Charset charset) {
this.defaultCharset = charset;
}
@CheckForNull
private CodecContext codecContext = null;
@Nonnull
private CodecContext getCodecContext() throws IllegalOptionsException {
if (codecContext == null) {
codecContext = new CodecContext();
if (workingDirectory != null) {
try {
codecContext.setWorkingDirectory(workingDirectory);
} catch (NotDirectoryException e) {
throw new IllegalOptionsException(e.getMessage(), e);
} catch (WrongPermissionException e) {
throw new IllegalOptionsException(e.getMessage(), e);
} catch (NoSuchFileException e) {
throw new IllegalOptionsException(e.getMessage(), e);
}
}
if (standardError != null) {
codecContext.setStandardError(standardError);
}
if (standardOutput != null) {
codecContext.setStandardOutput(standardOutput);
}
if (defaultCharset != null) {
codecContext.setDefaultCharset(defaultCharset);
}
if (sanityChecks) {
codecContext.setDebug();
}
}
assert codecContext != null;
return codecContext;
}
/*
* Plugin Manager
*/
@Option(name = "--list-plugins", usage = "display all available plugins")
private boolean listPlugins;
@Option(name = "--pluginpath", usage = "jack plugin classpath", metaVar = "<PATH>")
@Nonnull
private String pluginPath = "";
@Option(name = "--plugin", usage = "jack plugin names", metaVar = "<NAME>[,<NAME>...] (unique)")
@Nonnull
private String pluginNames = "";
public void setPluginPath(@CheckForNull String pluginPath) {
if (pluginPath == null) {
this.pluginPath = "";
} else {
this.pluginPath = pluginPath;
}
}
public void setPluginNames(@Nonnull String pluginNames) {
this.pluginNames = pluginNames;
}
@CheckForNull
private PluginManager pluginManager = null;
@Nonnull
public static final ListCodec<InputJarFile> PLUGIN_PATH_CODEC =
new ListCodec<InputJarFile>(new JackPluginJarCodec()).setSeparator(File.pathSeparator);
@Nonnull
public static final ListCodec<String> PLUGIN_NAMES_CODEC =
new ListCodec<String>(new StringValueCodec("a Jack plugin name", "plugin"))
.setSeparator(",")
.ensureUnicity();
public void ensurePluginManager()
throws IllegalOptionsException {
if (pluginManager == null) {
List<InputJarFile> jars;
try {
jars = PLUGIN_PATH_CODEC.checkString(getCodecContext(), pluginPath);
if (jars == null) {
jars = PLUGIN_PATH_CODEC.parseString(getCodecContext(), pluginPath);
}
pluginManager = new PluginManager();
try {
for (InputJarFile jar : jars) {
pluginManager.addPlugin(new URL[]{jar.getFile().toURI().toURL()});
}
} catch (NotJackPluginException | MalformedURLException e) {
throw new AssertionError(e);
}
} catch (ParsingException e) {
throw new IllegalOptionsException("option --pluginpath: " + e.getMessage(), e);
}
try {
List<String> names = PLUGIN_NAMES_CODEC.checkString(getCodecContext(), pluginNames);
if (names == null) {
names = PLUGIN_NAMES_CODEC.parseString(getCodecContext(), pluginNames);
}
for (String name : names) {
try {
assert pluginManager != null;
pluginManager.loadPlugin(name);
} catch (PluginNotFoundException e) {
throw new IllegalOptionsException("option --plugin: " + e.getMessage(), e);
}
}
} catch (ParsingException e) {
throw new IllegalOptionsException("option --plugin: " + e.getMessage(), e);
}
}
}
@Nonnull
public PluginManager getPluginManager() {
assert pluginManager != null;
return pluginManager;
}
/*
*
*/
public void setVerbosityLevel(@Nonnull VerbosityLevel verbose) {
this.verbose = verbose;
}
public boolean askForVersion() {
return version;
}
public boolean askForHelp() {
return help;
}
public boolean askForPropertiesHelp() {
return helpProperties;
}
public boolean askForPluginsList() {
return listPlugins;
}
public void setInputSources(@Nonnull Collection<File> inputSources) {
this.inputSources = new ArrayList<File>(inputSources);
}
public void setOutputDir(File out) {
this.out = out;
}
public void setOutputZip(File out) {
this.outZip = out;
}
@CheckForNull
private Config config = null;
@Nonnull
public Config getConfig() {
assert config != null;
return config;
}
@Nonnull
public GatherConfigBuilder getDefaultConfigBuilder()
throws IOException, IllegalOptionsException {
ensurePluginManager();
GatherConfigBuilder configBuilder = new GatherConfigBuilder(sanityChecks,
getPluginManager().getReflectionManager(ReflectionFactory.getManager()));
assert codecContext != null; // ensured by ensurePluginManager()
configBuilder.setCodecContext(codecContext);
String resourceName = "/config.properties";
InputStream is = Main.class.getResourceAsStream(resourceName);
if (is != null) {
try {
configBuilder.load(is, new StringLocation("resource " + resourceName));
} finally {
is.close();
}
}
return configBuilder;
}
@Nonnull
public GatherConfigBuilder getConfigBuilder(@Nonnull RunnableHooks hooks)
throws IllegalOptionsException {
GatherConfigBuilder configBuilder;
if (propertiesFile != null) {
if (!propertiesFile.exists()) {
throw new IllegalOptionsException(
"The specified config file '" + propertiesFile.getPath() + "' does not exist.");
}
if (!propertiesFile.isFile()) {
throw new IllegalOptionsException(
"The specified config file '" + propertiesFile.getPath() + "' is not a file.");
}
if (!propertiesFile.canRead()) {
throw new IllegalOptionsException(
"The specified config file '" + propertiesFile.getPath() + "' cannot be read.");
}
ensurePluginManager();
configBuilder = new GatherConfigBuilder(sanityChecks,
getPluginManager().getReflectionManager(ReflectionFactory.getManager()));
configBuilder.setCodecContext(getCodecContext());
try {
InputStream is = new BufferedInputStream(new FileInputStream(propertiesFile));
try {
configBuilder.load(is, new FileLocation(propertiesFile));
} finally {
is.close();
}
} catch (FileNotFoundException e) {
// Already check
throw new AssertionError();
} catch (IOException e) {
throw new IllegalOptionsException(
"The specified config file '" + propertiesFile.getPath() + "' cannot be read.",
e.getCause());
}
} else {
try {
configBuilder = getDefaultConfigBuilder();
} catch (IOException e) {
throw new IllegalOptionsException(e.getMessage(), e);
}
}
configBuilder.pushDefaultLocation(new StringLocation("Options"));
configBuilder.set(VERBOSITY_LEVEL, verbose);
if (reporterStream != null) {
configBuilder.set(Reporter.REPORTER_WRITER,
new WriterFile(reporterStream, new NoLocation()));
}
if (!jarjarRulesFiles.isEmpty()) {
configBuilder.set(PackageRenamer.JARJAR_ENABLED, true);
String sep = PackageRenamer.JARJAR_FILES.getCodec().getSeparator();
configBuilder.setString(PackageRenamer.JARJAR_FILES, Joiner.on(sep).join(jarjarRulesFiles));
configBuilder.set(Options.USE_PREBUILT_FROM_LIBRARY, false);
logger.log(Level.WARNING,
"Prebuilts from libraries are not used due to usage of jarjar");
}
if (processor != null) {
configBuilder.set(ANNOTATION_PROCESSOR_MANUAL, true);
configBuilder.setString(ANNOTATION_PROCESSOR_MANUAL_LIST, processor);
}
configBuilder.set(ANNOTATION_PROCESSOR_SOURCE_OUTPUT_DIR, createTempDir(hooks));
Directory annotationProcessorOutputClasses = createTempDir(hooks);
configBuilder.set(ANNOTATION_PROCESSOR_CLASS_OUTPUT_DIR, annotationProcessorOutputClasses);
addResource(annotationProcessorOutputClasses.getFile());
if (processorPath != null) {
configBuilder.set(ANNOTATION_PROCESSOR_PATH, true);
configBuilder.setString(ANNOTATION_PROCESSOR_PATH_LIST, processorPath);
}
if (annotationProcessorOption != null) {
configBuilder.set(ANNOTATION_PROCESSOR_OPTIONS, annotationProcessorOption);
}
if (!resImport.isEmpty()) {
configBuilder.setString(ResourceImporter.IMPORTED_RESOURCES,
Joiner.on(File.pathSeparator).join(resImport));
}
if (!metaImport.isEmpty()) {
configBuilder.setString(MetaImporter.IMPORTED_META,
Joiner.on(File.pathSeparator).join(metaImport));
}
if (inputSources != null && !inputSources.isEmpty()) {
configBuilder.setString(SOURCES, Joiner.on(File.pathSeparator).join(inputSources));
}
if (emitLocalDebugInfo != null) {
configBuilder.set(EMIT_LOCAL_DEBUG_INFO, emitLocalDebugInfo);
}
configBuilder.pushDefaultLocation(new StringLocation("proguard flags"));
configBuilder.set(SHRINKING_ENABLED, flags != null && flags.shrink());
configBuilder.set(OBFUSCATION_ENABLED, flags != null && flags.obfuscate());
if (flags != null) {
if (flags.obfuscate() || flags.shrink()) {
configBuilder.set(Options.USE_PREBUILT_FROM_LIBRARY, false);
logger
.log(
Level.WARNING,
"Prebuilts from libraries are not use due to usage of shrinking or obfuscation");
}
if (flags.obfuscate()) { // keepAttribute only makes sense when obfuscating
boolean emitRuntimeInvisibleAnnotation = flags.keepAttribute("RuntimeInvisibleAnnotations");
configBuilder.set(
AnnotationRemover.EMIT_SOURCE_RETENTION_ANNOTATION, emitRuntimeInvisibleAnnotation);
configBuilder.set(
AnnotationRemover.EMIT_CLASS_RETENTION_ANNOTATION, emitRuntimeInvisibleAnnotation);
configBuilder.set(AnnotationRemover.EMIT_RUNTIME_RETENTION_ANNOTATION,
flags.keepAttribute("RuntimeVisibleAnnotations"));
boolean emitRuntimeInvisibleParameterAnnotation =
flags.keepAttribute("RuntimeInvisibleParameterAnnotations");
configBuilder.set(
ParameterAnnotationRemover.EMIT_SOURCE_RETENTION_PARAMETER_ANNOTATION,
emitRuntimeInvisibleParameterAnnotation);
configBuilder.set(
ParameterAnnotationRemover.EMIT_CLASS_RETENTION_PARAMETER_ANNOTATION,
emitRuntimeInvisibleParameterAnnotation);
configBuilder.set(
ParameterAnnotationRemover.EMIT_RUNTIME_RETENTION_PARAMETER_ANNOTATION,
flags.keepAttribute("RuntimeVisibleParameterAnnotations"));
configBuilder.set(EMIT_LINE_NUMBER_DEBUG_INFO, flags.keepAttribute("LineNumberTable"));
configBuilder.set(EMIT_LOCAL_DEBUG_INFO, flags.keepAttribute("LocalVariableTable"));
}
configBuilder.set(Options.FLAGS, flags);
configBuilder.set(
Options.USE_MIXED_CASE_CLASSNAME, flags.getUseMixedCaseClassName());
configBuilder.set(Renamer.USE_UNIQUE_CLASSMEMBERNAMES,
flags.getUseUniqueClassMemberNames());
File mapping = flags.getObfuscationMapping();
if (mapping != null) {
configBuilder.set(Renamer.USE_MAPPING, true);
configBuilder.setString(Renamer.MAPPING_FILE, mapping.getPath());
} else {
configBuilder.set(Renamer.USE_MAPPING, false);
}
File seeds = flags.getSeedsFile();
if (seeds != null) {
configBuilder.setString(SeedPrinter.SEEDS_OUTPUT_FILE, seeds.getPath());
}
File dictionary = flags.getObfuscationDictionary();
if (dictionary != null) {
configBuilder.set(Renamer.USE_OBFUSCATION_DICTIONARY, true);
configBuilder.setString(Renamer.OBFUSCATION_DICTIONARY, dictionary.getPath());
} else {
configBuilder.set(Renamer.USE_OBFUSCATION_DICTIONARY, false);
}
File classDictionary = flags.getClassObfuscationDictionary();
if (classDictionary != null) {
configBuilder.set(Renamer.USE_CLASS_OBFUSCATION_DICTIONARY, true);
configBuilder.setString(Renamer.CLASS_OBFUSCATION_DICTIONARY,
classDictionary.getPath());
} else {
configBuilder.set(Renamer.USE_CLASS_OBFUSCATION_DICTIONARY, false);
}
File packageDictionary = flags.getPackageObfuscationDictionary();
if (packageDictionary != null) {
configBuilder.set(Renamer.USE_PACKAGE_OBFUSCATION_DICTIONARY, true);
configBuilder.setString(
Renamer.PACKAGE_OBFUSCATION_DICTIONARY, packageDictionary.getPath());
} else {
configBuilder.set(Renamer.USE_PACKAGE_OBFUSCATION_DICTIONARY, false);
}
configBuilder.set(MappingPrinter.MAPPING_OUTPUT_ENABLED, flags.printMapping());
File outputmapping = flags.getOutputMapping();
if (outputmapping != null) {
configBuilder.setString(MappingPrinter.MAPPING_OUTPUT_FILE,
outputmapping.getPath());
}
if (flags.getUseMixedCaseClassName()) {
configBuilder.setString(NameProviderFactory.NAMEPROVIDER, "mixed-case");
}
String packageForRenamedClasses = flags.getPackageForRenamedClasses();
if (packageForRenamedClasses != null) {
configBuilder.set(Renamer.REPACKAGE_CLASSES, true);
configBuilder.set(Renamer.PACKAGE_FOR_RENAMED_CLASSES, packageForRenamedClasses);
if (flags.getPackageForFlatHierarchy() != null) {
throw new IllegalOptionsException("Flatten package and repackage classes cannot be used"
+ " simultaneously");
}
} else {
configBuilder.set(Renamer.REPACKAGE_CLASSES, false);
}
String packageForRenamedPackages = flags.getPackageForFlatHierarchy();
if (packageForRenamedPackages != null) {
configBuilder.set(Renamer.FLATTEN_PACKAGE, true);
configBuilder.set(Renamer.PACKAGE_FOR_RENAMED_PACKAGES, packageForRenamedPackages);
} else {
configBuilder.set(Renamer.FLATTEN_PACKAGE, false);
}
String renameSourceFileAttribute = flags.getRenameSourceFileAttribute();
if (renameSourceFileAttribute != null) {
configBuilder.set(SourceFileRenamer.RENAME_SOURCEFILE, true);
configBuilder.set(SourceFileRenamer.NEW_SOURCEFILE_NAME,
new File(renameSourceFileAttribute));
} else {
configBuilder.set(SourceFileRenamer.RENAME_SOURCEFILE, false);
}
}
configBuilder.popDefaultLocation();
if (importedLibraries != null) {
configBuilder.setString(
IMPORTED_LIBRARIES, Joiner.on(File.pathSeparator).join(importedLibraries));
}
if (classpath != null) {
configBuilder.setString(CLASSPATH, classpath);
}
if (libraryOutZip != null) {
configBuilder.setString(LIBRARY_OUTPUT_ZIP, libraryOutZip.getPath());
configBuilder.set(LIBRARY_OUTPUT_CONTAINER_TYPE, Container.ZIP);
configBuilder.set(GENERATE_JACK_LIBRARY, true);
configBuilder.set(GENERATE_JAYCE_IN_LIBRARY, true);
configBuilder.set(GENERATE_DEPENDENCIES_IN_LIBRARY, true);
} else if (libraryOutDir != null) {
configBuilder.setString(LIBRARY_OUTPUT_DIR, libraryOutDir.getPath());
configBuilder.set(LIBRARY_OUTPUT_CONTAINER_TYPE, Container.DIR);
configBuilder.set(GENERATE_JACK_LIBRARY, true);
configBuilder.set(GENERATE_JAYCE_IN_LIBRARY, true);
configBuilder.set(GENERATE_DEPENDENCIES_IN_LIBRARY, true);
} else {
configBuilder.setString(Options.LIBRARY_OUTPUT_DIR, createTempDir(hooks).getPath());
configBuilder.set(LIBRARY_OUTPUT_CONTAINER_TYPE, Container.DIR);
configBuilder.set(GENERATE_JACK_LIBRARY, true);
}
switch (multiDexKind) {
case NATIVE:
configBuilder.setString(DexFileWriter.DEX_WRITING_POLICY, "multidex");
break;
case LEGACY:
configBuilder.setString(DexFileWriter.DEX_WRITING_POLICY, "multidex");
configBuilder.set(MultiDexLegacy.MULTIDEX_LEGACY, true);
break;
case NONE:
break;
default:
throw new AssertionError("Unsupported multi dex kind: '" + multiDexKind.name() + "'");
}
if (outZip != null) {
configBuilder.setString(DEX_OUTPUT_ZIP, outZip.getPath());
configBuilder.set(DEX_OUTPUT_CONTAINER_TYPE, Container.ZIP);
configBuilder.set(GENERATE_DEX_FILE, true);
configBuilder.set(AndroidCompatibilityChecker.CHECK_COMPATIBILITY, true);
} else if (out != null) {
configBuilder.setString(DEX_OUTPUT_DIR, out.getPath());
configBuilder.set(DEX_OUTPUT_CONTAINER_TYPE, Container.DIR);
configBuilder.set(GENERATE_DEX_FILE, true);
configBuilder.set(AndroidCompatibilityChecker.CHECK_COMPATIBILITY, true);
}
// use a variable to keep record of whether incremental compilation is enabled or not,
// because we cannot check the value through configBuilder
boolean isIncrementalEnabled = false;
if (incrementalFolder != null) {
if (multiDexKind == MultiDexKind.LEGACY) {
logger.log(Level.WARNING,
"Incremental mode is disabled due to multi-dex legacy mode");
} else if (flags != null && (flags.shrink() || flags.obfuscate())) {
logger.log(Level.WARNING,
"Incremental mode is disabled due to usage of shrinking or obfuscation");
} else if (!jarjarRulesFiles.isEmpty()) {
logger.log(Level.WARNING,
"Incremental mode is disabled due to usage of jarjar");
} else if (properties.containsKey(SOURCE_PATH.getName())) {
logger.log(Level.WARNING,
"Incremental mode is disabled due to usage of source path");
} else {
configBuilder.set(Options.INCREMENTAL_MODE, true);
configBuilder.setString(Options.INPUT_FILTER, "incremental");
configBuilder.set(Options.GENERATE_JACK_LIBRARY, true);
configBuilder.set(GENERATE_JAYCE_IN_LIBRARY, true);
configBuilder.set(GENERATE_DEPENDENCIES_IN_LIBRARY, true);
configBuilder.setString(Options.LIBRARY_OUTPUT_CONTAINER_TYPE, "dir");
configBuilder.setString(Options.LIBRARY_OUTPUT_DIR, incrementalFolder.getPath());
if (libraryOutZip != null) {
configBuilder.set(GENERATE_LIBRARY_FROM_INCREMENTAL_FOLDER, true);
}
isIncrementalEnabled = true;
}
}
if (tracerDir != null) {
configBuilder.setString(TracerFactory.TRACER, "html");
configBuilder.setString(StatsTracerFtl.TRACER_DIR, tracerDir.getPath());
}
configBuilder.set(SANITY_CHECKS, sanityChecks);
configBuilder.popDefaultLocation();
configBuilder.setString(CLASSPATH, classpath);
for (Entry<String, String> entry : properties.entrySet()) {
configBuilder.setString(entry.getKey(), entry.getValue(), new StringLocation("-D option"));
}
if (isIncrementalEnabled) {
// if the incremental compilation is enabled, the switch enum optimization cannot
// be enabled because it will generates non-deterministic code. This has to be done after
// -D options are set
configBuilder.set(OPTIMIZED_ENUM_SWITCH.getName(), SwitchEnumOptStrategy.NEVER);
}
configBuilder.processEnvironmentVariables("JACK_CONFIG_");
configBuilder.setHooks(hooks);
return configBuilder;
}
public void checkValidity(@Nonnull RunnableHooks hooks)
throws IllegalOptionsException, ConfigurationException {
ecjExtraArguments.clear();
config = getConfigBuilder(hooks).build();
// FINDBUGS
Config config = this.config;
assert config != null;
// FINDBUGS
assert config != null;
// Check ecj arguments
if (inputSources != null) {
if (config.get(VERBOSITY_LEVEL) == VerbosityLevel.ERROR) {
ecjExtraArguments.add(0, "-nowarn");
}
ecjExtraArguments.add("-source");
ecjExtraArguments.add(config.get(Options.JAVA_SOURCE_VERSION).toString());
if (!config.get(Options.ANNOTATION_PROCESSOR_ENABLED).booleanValue()) {
ecjExtraArguments.add("-proc:none");
}
if (config.get(Options.EMIT_CLASS_FILES).booleanValue()) {
ecjExtraArguments.add("-d");
ecjExtraArguments.add(config.get(Options.EMIT_CLASS_FILES_FOLDER).getPath());
}
}
// Check Jack arguments
if (config.get(CodeItemBuilder.EMIT_SYNTHETIC_LOCAL_DEBUG_INFO).booleanValue()
&& !config.get(Options.EMIT_LOCAL_DEBUG_INFO).booleanValue()) {
throw new PropertyIdException(CodeItemBuilder.EMIT_SYNTHETIC_LOCAL_DEBUG_INFO,
new NoLocation(),
"Impossible to emit synthetic debug info when not emitting debug info");
}
if (verbose == VerbosityLevel.DEBUG || verbose == VerbosityLevel.TRACE) {
config.get(Reporter.REPORTER).report(Severity.NON_FATAL, new DeprecatedVerbosity(verbose));
}
}
public void setJayceOutputDir(@Nonnull File outputDir) {
libraryOutDir = outputDir;
}
public void setJayceOutputZip(@Nonnull File outputZip) {
libraryOutZip = outputZip;
}
public void setImportedLibraries(@Nonnull List<File> importedLibraries) {
this.importedLibraries = importedLibraries;
}
@CheckForNull
public Flags getFlags() {
return flags;
}
public void setFlags(@Nonnull Flags flags) {
this.flags = flags;
}
public void applyShrobFlags() {
assert flags != null;
List<File> inJars = flags.getInJars();
if (inJars.size() > 0) {
importedLibraries = new ArrayList<File>(inJars.size());
importedLibraries.addAll(inJars);
}
List<File> outJars = flags.getOutJars();
if (outJars.size() > 0) {
File outJar = outJars.get(0);
if (outJar.isDirectory()) {
libraryOutDir = outJar;
} else {
libraryOutZip = outJar;
}
}
String libraryJars = flags.getLibraryJars();
if (libraryJars != null) {
if (classpath.isEmpty()) {
classpath = libraryJars;
} else {
classpath += File.pathSeparatorChar + libraryJars;
}
}
}
public void setClasspath(@CheckForNull String classpath) {
if (classpath == null) {
this.classpath = "";
} else {
this.classpath = classpath;
}
}
public void setMultiDexKind(@Nonnull MultiDexKind multiDexKind) {
this.multiDexKind = multiDexKind;
}
public void addProguardFlagsFile(@Nonnull File flags) {
if (proguardFlagsFiles == null) {
proguardFlagsFiles = new ArrayList<File>();
}
proguardFlagsFiles.add(flags);
}
public void addProperty(@Nonnull String propertyName, @Nonnull String propertyValue) {
properties.put(propertyName, propertyValue);
}
@Nonnull
List<String> getEcjExtraArguments() {
return ecjExtraArguments;
}
public void setProguardFlagsFile(@Nonnull List<File> proguardFlagsFiles) {
this.proguardFlagsFiles = proguardFlagsFiles;
}
public void setJarjarRulesFiles(@Nonnull List<File> jarjarRulesFiles) {
this.jarjarRulesFiles = jarjarRulesFiles;
}
public void disableDxOptimizations() {
properties.put(CodeItemBuilder.DEX_OPTIMIZE.getName(), "false");
}
public void setSanityChecks(boolean sanityChecks) {
this.sanityChecks = sanityChecks;
}
public void setIncrementalFolder(@Nonnull File incrementalFolder) {
this.incrementalFolder = incrementalFolder;
}
public void addResource(@Nonnull File resource) {
resImport.add(resource);
}
public void setResourceDirs(@Nonnull List<File> resourceDirs) {
resImport = new ArrayList<File>(resourceDirs);
}
public void setMetaDirs(@Nonnull List<File> metaDirs) {
metaImport = metaDirs;
}
public void setReporterStream(@Nonnull OutputStream reporterStream) {
this.reporterStream = reporterStream;
}
public List<File> getProguardFlagsFile() {
List<File> proguardFlagsFileFromWorkingDir = new ArrayList<File>(proguardFlagsFiles.size());
for (File proguardFlagsFile : proguardFlagsFiles) {
if (workingDirectory != null && !proguardFlagsFile.isAbsolute()) {
proguardFlagsFileFromWorkingDir.add(
new File(workingDirectory, proguardFlagsFile.getPath()));
} else {
proguardFlagsFileFromWorkingDir.add(proguardFlagsFile);
}
}
return proguardFlagsFileFromWorkingDir;
}
@Nonnull
private static Directory createTempDir(
@Nonnull RunnableHooks hooks) {
try {
File tmp = Files.createTempDir("jack-");
Directory dir = new Directory(tmp.getPath(), hooks, Existence.MUST_EXIST, Permission.WRITE,
ChangePermission.NOCHANGE);
hooks.addHook(new TempDirDeleter(dir));
return dir;
} catch (CannotChangePermissionException | CannotCreateFileException
| FileAlreadyExistsException | NotDirectoryException | NoSuchFileException
| WrongPermissionException e) {
throw new JackUserException(e);
}
}
private static class TempDirDeleter implements Runnable {
@Nonnull
private final Directory dir;
public TempDirDeleter(@Nonnull Directory dir) {
this.dir = dir;
}
@Override
public void run() {
try {
FileUtils.deleteDir(dir.getFile());
} catch (IOException e) {
throw new JackIOException(
"Failed to delete temporary " + dir.getLocation().getDescription(), e);
}
}
}
}