Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.robovm.compiler.config.Config Maven / Gradle / Ivy
/*
* Copyright (C) 2012 RoboVM AB
* Copyright (C) 2018 Daniel Thommes, NeverNull GmbH,
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY 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 program. If not, see .
*/
package org.robovm.compiler.config;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.robovm.compiler.Version;
import org.robovm.compiler.*;
import org.robovm.compiler.clazz.Clazz;
import org.robovm.compiler.clazz.Clazzes;
import org.robovm.compiler.clazz.Path;
import org.robovm.compiler.config.ConfigXmlEntries.*;
import org.robovm.compiler.config.StripArchivesConfig.StripArchivesBuilder;
import org.robovm.compiler.config.tools.Tools;
import org.robovm.compiler.llvm.DataLayout;
import org.robovm.compiler.log.Logger;
import org.robovm.compiler.plugin.*;
import org.robovm.compiler.plugin.annotation.AnnotationImplPlugin;
import org.robovm.compiler.plugin.debug.DebugInformationPlugin;
import org.robovm.compiler.plugin.debug.DebuggerLaunchPlugin;
import org.robovm.compiler.plugin.invokedynamic.InvokeDynamicCompilerPlugin;
import org.robovm.compiler.plugin.objc.*;
import org.robovm.compiler.target.ConsoleTarget;
import org.robovm.compiler.target.Target;
import org.robovm.compiler.target.framework.FrameworkTarget;
import org.robovm.compiler.target.ios.IOSTarget;
import org.robovm.compiler.target.ios.ProvisioningProfile;
import org.robovm.compiler.target.ios.SigningIdentity;
import org.robovm.compiler.util.DigestUtil;
import org.robovm.compiler.util.InfoPList;
import org.robovm.compiler.util.io.RamDiskTools;
import org.simpleframework.xml.*;
import org.simpleframework.xml.convert.Converter;
import org.simpleframework.xml.convert.Registry;
import org.simpleframework.xml.convert.RegistryStrategy;
import org.simpleframework.xml.core.Persist;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.filter.PlatformFilter;
import org.simpleframework.xml.stream.Format;
import org.simpleframework.xml.stream.InputNode;
import org.simpleframework.xml.stream.OutputNode;
import org.simpleframework.xml.transform.RegistryMatcher;
import org.simpleframework.xml.transform.Transform;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Supplier;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Holds compiler configuration.
*/
@Root
public class Config {
/**
* The max file name length of files stored in the cache. OS X has a limit
* of 255 characters. Class names are very unlikely to be this long but some
* JVM language compilers (e.g. the Scala compiler) are known to generate
* very long class names for auto-generated classes. See #955.
*/
private static final int MAX_FILE_NAME_LENGTH = 255;
public enum Cacerts {
full
}
public enum TreeShakerMode {
none, conservative, aggressive
}
@Element(required = false)
private File installDir = null;
@Element(required = false)
private String executableName = null;
@Element(required = false)
private String imageName = null;
@Element(required = false)
private Boolean skipRuntimeLib = null;
@Element(required = false)
private File mainJar;
@Element(required = false)
private String mainClass;
@Element(required = false)
private Cacerts cacerts = null;
@Element(required = false)
private OS os = null;
@ElementList(required = false, inline = true)
private ArrayList archs = null;
@Element(required = false)
private RootsList roots;
@Element(required = false)
private ForceLinkClassesList forceLinkClasses;
@Element(required = false)
private ForceLinkMethodsList forceLinkMethods;
@Element(required = false)
private LibsList libs;
@Element(required = false)
private SymbolsList exportedSymbols;
@Element(required = false)
private SymbolsList unhideSymbols;
@Element(required = false)
private FrameworksList frameworks;
@Element(required = false)
private FrameworksList weakFrameworks;
@Element(required = false)
private PathsList frameworkPaths;
@Element(required = false)
private PathsList xcFrameworks;
@Element(required = false)
private AppExtensionsList appExtensions;
@Element(required = false)
private PathsList appExtensionPaths;
@Element(required = false)
private SwiftSupport swiftSupport = new SwiftSupport();
@Element(required = false)
private ExperimentalFeatures experimental = new ExperimentalFeatures();
@Element(required = false)
private ResourcesList resources;
@Element(required = false)
private ClasspathentryList bootclasspath;
@Element(required = false)
private ClasspathentryList classpath;
@Element(required = false)
private PluginArgumentsList pluginArguments;
@Element(required = false, name = "target")
private String targetType;
@Element(required = false, name = "stripArchives")
private StripArchivesConfig stripArchivesConfig;
@Element(required = false, name = "treeShaker")
private TreeShakerMode treeShakerMode;
@Element(required = false, name = "smartSkipRebuild")
private Boolean smartSkipRebuild;
@Element(required = false)
private String iosSdkVersion;
@Element(required = false, name = "iosInfoPList")
private File iosInfoPListFile = null;
@Element(required = false, name = "infoPList")
private File infoPListFile = null;
@Element(required = false)
private File iosEntitlementsPList;
@Element(required = false)
private WatchKitApp watchKitApp;
@Element(required = false)
private Tools tools;
@Element(required = false)
private Boolean enableBitcode;
private SigningIdentity iosSignIdentity;
private ProvisioningProfile iosProvisioningProfile;
private String iosDeviceType;
private InfoPList infoPList;
private boolean iosSkipSigning = false;
private Properties properties = new Properties();
private Home home = null;
private File tmpDir;
private File cacheDir = new File(System.getProperty("user.home"), ".robovm/cache");
private File ccBinPath = null;
private boolean clean = false;
private boolean debug = false;
private boolean useDebugLibs = false;
private boolean skipLinking = false;
private boolean skipInstall = false;
private boolean dumpIntermediates = false;
private boolean manuallyPreparedForLaunch = false;
private int threads = Runtime.getRuntime().availableProcessors();
private Logger logger = Logger.NULL_LOGGER;
/*
* The fields below are all initialized in build() and must not be included
* when constructing Config clone. We mark them as transient which will make
* the builder() method skip them.
*/
private transient final UUID buildUuid;
private transient List plugins = new ArrayList<>();
private transient Target target = null;
private transient File osArchDepLibDir;
private transient File osArchCacheDir;
private transient Clazzes clazzes;
private transient VTable.Cache vtableCache;
private transient ITable.Cache itableCache;
private transient List resourcesPaths = new ArrayList<>();
private transient DataLayout dataLayout;
private transient MarshalerLookup marshalerLookup;
private transient Config configBeforeBuild;
private transient DependencyGraph dependencyGraph;
private transient Arch sliceArch;
private transient StripArchivesBuilder stripArchivesBuilder;
private transient ResolvedLocations resolvedLocations;
protected Config(UUID uuid) {
// save session uuid
this.buildUuid = uuid;
// Add standard plugins
this.plugins.addAll(0, Arrays.asList(
new InterfaceBuilderClassesPlugin(),
new ObjCProtocolProxyPlugin(),
new ObjCProtocolToObjCObjectPlugin(),
new ObjCMemberPlugin(),
new ObjCBlockPlugin(),
new AnnotationImplPlugin(),
new InvokeDynamicCompilerPlugin(),
new DebugInformationPlugin(),
new DebuggerLaunchPlugin(),
new BuildGarbageCollectorPlugin()
));
this.loadPluginsFromClassPath();
}
/**
* Returns a new {@link Builder} which builds exactly this {@link Config}
* when {@link Builder#build()} is called.
*/
public Builder builder() throws IOException {
return new Builder(clone(configBeforeBuild));
}
public UUID getBuildUuid() {
return buildUuid;
}
public Home getHome() {
return home;
}
public File getInstallDir() {
return installDir;
}
public String getExecutableName() {
return executableName;
}
public String getImageName() {
return imageName;
}
public File getExecutablePath() {
return new File(installDir, getExecutableName());
}
public File getImagePath() {
return getExecutablePath();
}
public File getCacheDir() {
return osArchCacheDir;
}
public File getCcBinPath() {
return ccBinPath;
}
public OS getOs() {
return os;
}
public Arch getArch() {
return sliceArch;
}
public List getArchs() {
return archs == null ? Collections.emptyList()
: Collections.unmodifiableList(archs);
}
public String getTriple() {
return getTriple(os.getMinVersion());
}
public String getTriple(String minVersion) {
return sliceArch.getCpuArch().getLlvmName() + "-" + os.getVendor() + "-" + os.getLlvmName() + minVersion +
sliceArch.getEnv().asLlvmSuffix("-");
}
public String getClangTriple() {
return getClangTriple(os.getMinVersion());
}
public String getClangTriple(String minVersion) {
return sliceArch.getCpuArch().getClangName() + "-" + os.getVendor() + "-" + os.getLlvmName() + minVersion +
sliceArch.getEnv().asLlvmSuffix("-");
}
public DataLayout getDataLayout() {
return dataLayout;
}
public boolean isClean() {
return clean;
}
public boolean isDebug() {
return debug;
}
public boolean isUseDebugLibs() {
return useDebugLibs;
}
public boolean isDumpIntermediates() {
return dumpIntermediates;
}
public boolean isManuallyPreparedForLaunch() {
return manuallyPreparedForLaunch;
}
public boolean isSkipRuntimeLib() {
return skipRuntimeLib != null && skipRuntimeLib;
}
public boolean isSkipLinking() {
return skipLinking;
}
public boolean isSkipInstall() {
return skipInstall;
}
public int getThreads() {
return threads;
}
public File getMainJar() {
return mainJar;
}
public String getMainClass() {
return mainClass;
}
public Cacerts getCacerts() {
return cacerts == null ? Cacerts.full : cacerts;
}
public List getResourcesPaths() {
return resourcesPaths;
}
public void addResourcesPath(Path path) {
resourcesPaths.add(path);
}
public StripArchivesConfig getStripArchivesConfig() {
return stripArchivesConfig == null ? StripArchivesConfig.DEFAULT : stripArchivesConfig;
}
public DependencyGraph getDependencyGraph() {
if (dependencyGraph == null)
throw new IllegalStateException(".dependencyGraph has been disposed!");
return dependencyGraph;
}
public File getTmpDir() {
if (tmpDir == null) {
try {
tmpDir = File.createTempFile("robovm", ".tmp");
} catch (IOException e) {
throw new RuntimeException(e);
}
tmpDir.delete();
tmpDir.mkdirs();
}
return tmpDir;
}
public List getForceLinkClasses() {
return forceLinkClasses == null ? Collections.emptyList()
: Collections.unmodifiableList(forceLinkClasses);
}
public List getForceLinkMethods() {
return forceLinkMethods == null ? Collections.emptyList()
: Collections.unmodifiableList(forceLinkMethods);
}
public List getExportedSymbols() {
return exportedSymbols == null ? Collections.emptyList()
: Collections.unmodifiableList(exportedSymbols);
}
public List getUnhideSymbols() {
return unhideSymbols == null ? Collections.emptyList()
: Collections.unmodifiableList(unhideSymbols);
}
public List getLibs() {
return getResolvedLocations().libs;
}
public List getFrameworks() {
return getResolvedLocations().frameworks;
}
public List getWeakFrameworks() {
return getResolvedLocations().weakFrameworks;
}
private synchronized ResolvedLocations getResolvedLocations() {
if (resolvedLocations == null)
resolvedLocations = resolveLocations();
return resolvedLocations;
}
public List getFrameworkPaths() {
return getResolvedLocations().frameworkPaths;
}
public List getAppExtensions() {
return appExtensions == null ? Collections.emptyList()
: Collections.unmodifiableList(appExtensions);
}
public List getAppExtensionPaths() {
return appExtensionPaths == null ? Collections.emptyList()
: appExtensionPaths.stream()
.filter(this::isQualified)
.map(e -> e.entry)
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
}
public SwiftSupport getSwiftSupport() {
return swiftSupport.isEnabled() ? swiftSupport : null;
}
public boolean hasSwiftSupport() {
return swiftSupport.isEnabled();
}
public List getSwiftLibPaths() {
return !swiftSupport.isEnabled() ? Collections.emptyList()
: swiftSupport.getSwiftLibPaths().stream()
.filter(this::isQualified)
.map(f -> f.entry)
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
}
public List getResources() {
return resources == null ? Collections.emptyList()
: Collections.unmodifiableList(resources);
}
public File getOsArchDepLibDir() {
return osArchDepLibDir;
}
public Clazzes getClazzes() {
if (clazzes == null)
throw new IllegalStateException(".clazzes has been disposed!");
return clazzes;
}
public void disposeBuildData() {
// not null clazzes as some data like allPath is required post-build (e.g. to stripArchives)
clazzes.disposeData();
dependencyGraph = null;
vtableCache = null;
itableCache = null;
marshalerLookup = null;
}
public VTable.Cache getVTableCache() {
if (vtableCache == null)
throw new IllegalStateException(".vtableCache has been disposed!");
return vtableCache;
}
public ITable.Cache getITableCache() {
if (itableCache == null)
throw new IllegalStateException(".itableCache has been disposed!");
return itableCache;
}
public MarshalerLookup getMarshalerLookup() {
return marshalerLookup;
}
public List getCompilerPlugins() {
List compilerPlugins = new ArrayList<>();
for (Plugin plugin : plugins) {
if (plugin instanceof CompilerPlugin) {
compilerPlugins.add((CompilerPlugin) plugin);
}
}
return compilerPlugins;
}
public List getLaunchPlugins() {
List launchPlugins = new ArrayList<>();
for (Plugin plugin : plugins) {
if (plugin instanceof LaunchPlugin) {
launchPlugins.add((LaunchPlugin) plugin);
}
}
return launchPlugins;
}
public List getTargetPlugins() {
List targetPlugins = new ArrayList<>();
for (Plugin plugin : plugins) {
if (plugin instanceof TargetPlugin) {
targetPlugins.add((TargetPlugin) plugin);
}
}
return targetPlugins;
}
public List getPlugins() {
return plugins;
}
public List getPluginArguments() {
return pluginArguments == null ? Collections.emptyList()
: Collections.unmodifiableList(pluginArguments);
}
public List getBootclasspath() {
return bootclasspath == null ? Collections.emptyList()
: Collections.unmodifiableList(bootclasspath);
}
public List getClasspath() {
return classpath == null ? Collections.emptyList()
: Collections.unmodifiableList(classpath);
}
public Properties getProperties() {
return properties;
}
public Logger getLogger() {
return logger;
}
public Target getTarget() {
return target;
}
public String getTargetType() {
return targetType;
}
public TreeShakerMode getTreeShakerMode() {
return treeShakerMode == null ? TreeShakerMode.none : treeShakerMode;
}
public boolean isSmartSkipRebuild(){
return smartSkipRebuild != null && smartSkipRebuild;
}
public String getIosSdkVersion() {
return iosSdkVersion;
}
public String getIosDeviceType() {
return iosDeviceType;
}
public InfoPList getIosInfoPList() {
return getInfoPList();
}
public InfoPList getInfoPList() {
if (infoPList == null && iosInfoPListFile != null) {
infoPList = new InfoPList(iosInfoPListFile);
} else if (infoPList == null && infoPListFile != null) {
infoPList = new InfoPList(infoPListFile);
}
return infoPList;
}
public File getIosEntitlementsPList() {
return iosEntitlementsPList;
}
public SigningIdentity getIosSignIdentity() {
return iosSignIdentity;
}
public ProvisioningProfile getIosProvisioningProfile() {
return iosProvisioningProfile;
}
public boolean isIosSkipSigning() {
return iosSkipSigning;
}
public boolean isEnableBitcode() {return enableBitcode != null && enableBitcode && shouldEmitBitcode(); }
public boolean shouldEmitBitcode() {
// emit bitcode to object file even if it is not enabled.
// as currently only `__LLVM,__asm` is being added
// but build should match criteria
return !debug && os == OS.ios && sliceArch.getEnv() == Environment.Native &&
(sliceArch.getCpuArch() == CpuArch.arm64 || sliceArch.getCpuArch() == CpuArch.thumbv7);
}
public Tools getTools() {
return tools;
}
public WatchKitApp getWatchKitApp() {
return watchKitApp;
}
private static File makeFileRelativeTo(File dir, File f) {
if (f.getParentFile() == null) {
return dir;
}
return new File(makeFileRelativeTo(dir, f.getParentFile()), f.getName());
}
public String getArchiveName(Path path) {
if (path.getFile().isFile()) {
return path.getFile().getName();
} else {
return "classes" + path.getIndex() + ".jar";
}
}
static String getFileName(Clazz clazz, String ext) {
return getFileName(clazz.getInternalName(), ext, MAX_FILE_NAME_LENGTH);
}
static String getFileName(String internalName, String ext, int maxFileNameLength) {
String packagePath = internalName.substring(0, internalName.lastIndexOf('/') + 1);
String className = internalName.substring(internalName.lastIndexOf('/') + 1);
String suffix = ext.startsWith(".") ? ext : "." + ext;
int length = className.length() + suffix.length();
if (length > maxFileNameLength) {
String sha1 = DigestUtil.sha1(className);
className = className.substring(0, Math.max(0, maxFileNameLength - suffix.length() - sha1.length())) + sha1;
}
return packagePath.replace('/', File.separatorChar) + className + suffix;
}
public File getLlFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.ll"));
}
public File getCFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.c"));
}
public File getBcFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.bc"));
}
public File getSFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.s"));
}
public File getOFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.o"));
}
public File getLinesOFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.lines.o"));
}
public File getLinesLlFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.lines.ll"));
}
public File getDebugInfoOFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.debuginfo.o"));
}
public File getDebugInfoLlFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.debuginfo.ll"));
}
public File getInfoFile(Clazz clazz) {
return new File(getCacheDir(clazz.getPath()), getFileName(clazz, "class.info"));
}
public File getCacheDir(Path path) {
File srcRoot = path.getFile().getAbsoluteFile().getParentFile();
String name = path.getFile().getName();
try {
return new File(makeFileRelativeTo(osArchCacheDir, srcRoot.getCanonicalFile()), name);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Returns the directory where generated classes are stored for the
* specified {@link Path}. Generated classes are stored in the cache
* directory in a dir at the same level as the cache dir for the
* {@link Path} with .generated
appended to the dir name.
*/
public File getGeneratedClassDir(Path path) {
File pathCacheDir = getCacheDir(path);
return new File(pathCacheDir.getParentFile(), pathCacheDir.getName() + ".generated");
}
/**
* tests if qualified item matches this config
*/
protected boolean isQualified(Qualified qualified) {
if (qualified.filterPlatforms() != null) {
if (!Arrays.asList(qualified.filterPlatforms()).contains(os))
return false;
}
if (qualified.filterArch() != null) {
if (Arrays.stream(qualified.filterArch()).noneMatch((a) -> a.promoteTo(os).equals(sliceArch)))
return false;
}
if (qualified.filterPlatformVariants() != null) {
PlatformVariant variant = (sliceArch.getEnv() == Environment.Native) ? PlatformVariant.device : PlatformVariant.simulator;
return Arrays.asList(qualified.filterPlatformVariants()).contains(variant);
}
return true;
}
private static Map getManifestAttributes(File jarFile) throws IOException {
try (JarFile jf = new JarFile(jarFile)) {
return new HashMap<>(jf.getManifest().getMainAttributes());
}
}
private static String getImplementationVersion(File jarFile) throws IOException {
return (String) getManifestAttributes(jarFile).get(Attributes.Name.IMPLEMENTATION_VERSION);
}
private static String getMainClass(File jarFile) throws IOException {
return (String) getManifestAttributes(jarFile).get(Attributes.Name.MAIN_CLASS);
}
private File extractIfNeeded(Path path) throws IOException {
if (path.getFile().isFile()) {
File pathCacheDir = getCacheDir(path);
File target = new File(pathCacheDir.getParentFile(), pathCacheDir.getName() + ".extracted");
if (!target.exists() || path.getFile().lastModified() > target.lastModified()) {
FileUtils.deleteDirectory(target);
target.mkdirs();
try (ZipFile zipFile = new ZipFile(path.getFile())) {
Enumeration extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().startsWith("META-INF/robovm/") && !entry.isDirectory()) {
File f = new File(target, entry.getName());
f.getParentFile().mkdirs();
try (InputStream in = zipFile.getInputStream(entry);
OutputStream out = new FileOutputStream(f)) {
IOUtils.copy(in, out);
if (entry.getTime() != -1) {
f.setLastModified(entry.getTime());
}
}
}
}
}
target.setLastModified(path.getFile().lastModified());
}
return target;
} else {
return path.getFile();
}
}
private > T mergeLists(T from, T to, Supplier creator) {
if (from == null) {
return to;
}
to = to != null ? to : creator.get();
for (E o : from) {
if (!to.contains(o)) {
to.add(o);
}
}
return to;
}
private void mergeConfig(Config from, Config to) {
to.exportedSymbols = mergeLists(from.exportedSymbols, to.exportedSymbols, SymbolsList::new);
to.unhideSymbols = mergeLists(from.unhideSymbols, to.unhideSymbols, SymbolsList::new);
to.forceLinkClasses = mergeLists(from.forceLinkClasses, to.forceLinkClasses, ForceLinkClassesList::new);
to.forceLinkMethods = mergeLists(from.forceLinkMethods, to.forceLinkMethods, ForceLinkMethodsList::new);
to.frameworkPaths = mergeLists(from.frameworkPaths, to.frameworkPaths, PathsList::new);
to.xcFrameworks = mergeLists(from.xcFrameworks, to.xcFrameworks, PathsList::new);
to.frameworks = mergeLists(from.frameworks, to.frameworks, FrameworksList::new);
to.libs = mergeLists(from.libs, to.libs, LibsList::new);
to.resources = mergeLists(from.resources, to.resources, ResourcesList::new);
to.weakFrameworks = mergeLists(from.weakFrameworks, to.weakFrameworks, FrameworksList::new);
}
private void mergeConfigsFromClasspath() throws IOException {
List dirs = Arrays.asList(
"META-INF/robovm/" + os + "/" + sliceArch,
"META-INF/robovm/" + os);
// The algorithm below preserves the order of config data from the
// classpath. Last the config from this object is added.
// First merge all configs on the classpath to an empty Config
Config config = new Config(this.buildUuid);
for (Path path : clazzes.getPaths()) {
for (String dir : dirs) {
if (path.contains(dir + "/robovm.xml")) {
File configXml = new File(new File(extractIfNeeded(path), dir), "robovm.xml");
Builder builder = new Builder();
builder.read(configXml);
mergeConfig(builder.config, config);
break;
}
}
}
// Then merge with this Config
mergeConfig(this, config);
// Copy back to this Config
this.exportedSymbols = config.exportedSymbols;
this.unhideSymbols = config.unhideSymbols;
this.forceLinkClasses = config.forceLinkClasses;
this.forceLinkMethods = config.forceLinkMethods;
this.frameworkPaths = config.frameworkPaths;
this.frameworks = config.frameworks;
this.libs = config.libs;
this.resources = config.resources;
this.weakFrameworks = config.weakFrameworks;
}
private static List toList(Iterator it) {
List l = new ArrayList<>();
while (it.hasNext()) {
l.add(it.next());
}
return l;
}
private void loadPluginsFromClassPath() {
ClassLoader classLoader = getClass().getClassLoader();
ServiceLoader compilerPluginLoader = ServiceLoader.load(CompilerPlugin.class, classLoader);
ServiceLoader launchPluginLoader = ServiceLoader.load(LaunchPlugin.class, classLoader);
ServiceLoader targetPluginLoader = ServiceLoader.load(TargetPlugin.class, classLoader);
plugins.addAll(toList(compilerPluginLoader.iterator()));
plugins.addAll(toList(launchPluginLoader.iterator()));
plugins.addAll(toList(targetPluginLoader.iterator()));
}
private static Config clone(Config config) throws IOException {
Config clone = new Config(config.buildUuid);
for (Field f : Config.class.getDeclaredFields()) {
if (!Modifier.isStatic(f.getModifiers()) && !Modifier.isTransient(f.getModifiers())) {
f.setAccessible(true);
try {
Object o = f.get(config);
if (o instanceof Collection && o instanceof Cloneable) {
// Clone collections. Assume the class has a public
// clone() method.
Method m = o.getClass().getMethod("clone");
o = m.invoke(o);
}
f.set(clone, o);
} catch (Throwable t) {
throw new Error(t);
}
}
}
return clone;
}
private Config build() throws IOException {
// drop any resolved entities to have it re-resolved with updated data
resolvedLocations = null;
// Create a clone of this Config before we have done anything with it so
// that builder() has a fresh Config it can use.
this.configBeforeBuild = clone(this);
if (home == null) {
home = Home.find();
}
if (bootclasspath == null) {
bootclasspath = new ClasspathentryList();
}
if (classpath == null) {
classpath = new ClasspathentryList();
}
if (mainJar != null) {
mainClass = getMainClass(mainJar);
classpath.add(mainJar);
}
if (executableName == null && imageName != null) {
executableName = imageName;
}
if (!skipLinking && executableName == null && mainClass == null) {
throw new IllegalArgumentException("No target and no main class specified");
}
if (!skipLinking && classpath.isEmpty()) {
throw new IllegalArgumentException("No classpath specified");
}
if (skipLinking) {
skipInstall = true;
}
if (executableName == null) {
executableName = mainClass;
}
if (imageName == null || !imageName.equals(executableName)) {
imageName = executableName;
}
// promote environment of arch if it is not ambiguous (e.g. x86_64 or iOS exists only
// in simulator environment)
if (archs != null) {
archs.replaceAll(arch -> arch.promoteTo(os));
}
List realBootclasspath = bootclasspath == null ? new ArrayList<>() : bootclasspath;
if (!isSkipRuntimeLib()) {
realBootclasspath = new ArrayList<>(bootclasspath);
realBootclasspath.add(0, home.rtPath);
}
this.vtableCache = new VTable.Cache();
this.itableCache = new ITable.Cache();
this.marshalerLookup = new MarshalerLookup(this);
if (!skipInstall) {
if (installDir == null) {
installDir = new File(".", executableName);
}
installDir.mkdirs();
}
if (targetType != null) {
if (ConsoleTarget.TYPE.equals(targetType)) {
target = new ConsoleTarget();
} else if (IOSTarget.TYPE.equals(targetType)) {
target = new IOSTarget();
} else if (FrameworkTarget.matches(targetType)) {
target = new FrameworkTarget(targetType);
} else {
for (TargetPlugin plugin : getTargetPlugins()) {
if (plugin.getTarget().getType().equals(targetType)) {
target = plugin.getTarget();
break;
}
}
if (target == null) {
throw new IllegalArgumentException("Unsupported target '" + targetType + "'");
}
}
} else {
// Auto
if (os == OS.ios) {
target = new IOSTarget();
} else {
target = new ConsoleTarget();
}
}
if (!getArchs().isEmpty()) {
sliceArch = getArchs().get(0);
}
target.init(this);
os = target.getOs();
sliceArch = target.getArch();
dataLayout = new DataLayout(getTriple());
osArchDepLibDir = new File(new File(home.libVmDir, os.toString()), sliceArch.toString());
dependencyGraph = new DependencyGraph(getTreeShakerMode());
RamDiskTools ramDiskTools = new RamDiskTools();
ramDiskTools.setupRamDisk(this, this.cacheDir, this.tmpDir);
this.cacheDir = ramDiskTools.getCacheDir();
this.tmpDir = ramDiskTools.getTmpDir();
File osDir = new File(cacheDir, os.toString());
String archName = sliceArch.toString();
File archDir = new File(osDir, archName);
osArchCacheDir = new File(archDir, debug ? "debug" : "release");
osArchCacheDir.mkdirs();
this.clazzes = new Clazzes(this, realBootclasspath, classpath);
if(this.stripArchivesConfig == null) {
if(stripArchivesBuilder == null) {
this.stripArchivesConfig = StripArchivesConfig.DEFAULT;
}
else {
this.stripArchivesConfig = stripArchivesBuilder.build();
}
}
mergeConfigsFromClasspath();
return this;
}
public static class Home {
private File binDir;
private File libVmDir;
private File rtPath;
private Map cacertsPath;
private boolean dev = false;
public Home(File homeDir) {
this(homeDir, true);
}
protected Home(File homeDir, boolean validate) {
if (validate) {
validate(homeDir);
}
binDir = new File(homeDir, "bin");
libVmDir = new File(homeDir, "lib/vm");
rtPath = new File(homeDir, "lib/robovm-rt.jar");
cacertsPath = new HashMap<>();
cacertsPath.put(Cacerts.full, new File(homeDir, "lib/robovm-cacerts-full.jar"));
}
private Home(File devDir, File binDir, File libVmDir, File rtPath) {
this.binDir = binDir;
this.libVmDir = libVmDir;
this.rtPath = rtPath;
cacertsPath = new HashMap<>();
cacertsPath.put(Cacerts.full, new File(devDir,
"cacerts/full/target/robovm-cacerts-full-" + Version.getCompilerVersion() + ".jar"));
this.dev = true;
}
public boolean isDev() {
return dev;
}
public File getBinDir() {
return binDir;
}
public File getLibVmDir() {
return libVmDir;
}
public File getRtPath() {
return rtPath;
}
public File getCacertsPath(Cacerts cacerts) {
return cacertsPath.get(cacerts);
}
public static Home find() {
// Check if ROBOVM_DEV_ROOT has been set. If set it should be
// pointing at the root of a complete RoboVM source tree.
if (System.getenv("ROBOVM_DEV_ROOT") != null) {
File dir = new File(System.getenv("ROBOVM_DEV_ROOT"));
return validateDevRootDir(dir);
}
if (System.getProperty("ROBOVM_DEV_ROOT") != null) {
File dir = new File(System.getProperty("ROBOVM_DEV_ROOT"));
return validateDevRootDir(dir);
}
if (System.getenv("ROBOVM_HOME") != null) {
File dir = new File(System.getenv("ROBOVM_HOME"));
return new Home(dir);
}
List candidates = new ArrayList<>();
File userHome = new File(System.getProperty("user.home"));
candidates.add(new File(userHome, "Applications/robovm"));
candidates.add(new File(userHome, ".robovm/home"));
candidates.add(new File("/usr/local/lib/robovm"));
candidates.add(new File("/opt/robovm"));
candidates.add(new File("/usr/lib/robovm"));
for (File dir : candidates) {
if (dir.exists()) {
return new Home(dir);
}
}
throw new IllegalArgumentException("ROBOVM_HOME not set and no RoboVM "
+ "installation found in " + candidates);
}
public static void validate(File dir) {
String error = "Path " + dir + " is not a valid RoboVM install directory: ";
// Check for required dirs and match the compiler version with our
// version.
if (!dir.exists()) {
throw new IllegalArgumentException(error + "no such path");
}
if (!dir.isDirectory()) {
throw new IllegalArgumentException(error + "not a directory");
}
File libDir = new File(dir, "lib");
if (!libDir.exists() || !libDir.isDirectory()) {
throw new IllegalArgumentException(error + "lib/ missing or invalid");
}
File binDir = new File(dir, "bin");
if (!binDir.exists() || !binDir.isDirectory()) {
throw new IllegalArgumentException(error + "bin/ missing or invalid");
}
File libVmDir = new File(libDir, "vm");
if (!libVmDir.exists() || !libVmDir.isDirectory()) {
throw new IllegalArgumentException(error + "lib/vm/ missing or invalid");
}
File rtJarFile = new File(libDir, "robovm-rt.jar");
if (!rtJarFile.exists() || !rtJarFile.isFile()) {
throw new IllegalArgumentException(error
+ "lib/robovm-rt.jar missing or invalid");
}
// Compare the version of this compiler with the version of the
// robovm-rt.jar in the home dir. They have to match.
try {
String thisVersion = Version.getCompilerVersion();
String thatVersion = getImplementationVersion(rtJarFile);
if (thisVersion == null || !thisVersion.equals(thatVersion)) {
throw new IllegalArgumentException(error + "version mismatch (expected: "
+ thisVersion + ", was: " + thatVersion + ")");
}
} catch (IOException e) {
throw new IllegalArgumentException(error
+ "failed to get version of rt jar", e);
}
}
private static Home validateDevRootDir(File dir) {
String error = "Path " + dir + " is not a valid RoboVM source tree: ";
// Check for required dirs.
if (!dir.exists()) {
throw new IllegalArgumentException(error + "no such path");
}
if (!dir.isDirectory()) {
throw new IllegalArgumentException(error + "not a directory");
}
File vmBinariesDir = new File(dir, "vm/target/binaries");
if (!vmBinariesDir.exists() || !vmBinariesDir.isDirectory()) {
throw new IllegalArgumentException(error + "vm/target/binaries/ missing or invalid");
}
File binDir = new File(dir, "bin");
if (!binDir.exists() || !binDir.isDirectory()) {
throw new IllegalArgumentException(error + "bin/ missing or invalid");
}
String rtJarName = "robovm-rt-" + Version.getCompilerVersion() + ".jar";
File rtJar = new File(dir, "rt/target/" + rtJarName);
File rtClasses = new File(dir, "rt/target/classes/");
File rtSource = rtJar;
if (!rtJar.exists() || rtJar.isDirectory()) {
if (!rtClasses.exists() || rtClasses.isFile()) {
throw new IllegalArgumentException(error
+ "rt/target/" + rtJarName + " missing or invalid");
} else {
rtSource = rtClasses;
}
}
return new Home(dir, binDir, vmBinariesDir, rtSource);
}
}
/**
* reads configuration from disk without any analysis
*/
public static Config loadRawConfig(File contentRoot) throws IOException {
// dkimitsa: config retrieved this way shall not be used for any compilation needs
// just for IB and other UI related things
Builder builder = new Builder();
builder.readProjectProperties(contentRoot, false);
builder.readProjectConfig(contentRoot, false);
return builder.config;
}
@SuppressWarnings({"UnusedReturnValue", "unused"})
public static class Builder {
protected final Config config;
Builder(Config config) {
this.config = config;
}
public Builder() {
this.config = new Config(UUID.randomUUID());
}
public Builder os(OS os) {
config.os = os;
return this;
}
public Builder arch(Arch arch) {
return archs(arch);
}
public Builder archs(Arch ... archs) {
return archs(Arrays.asList(archs));
}
public Builder archs(List archs) {
if (config.archs == null) {
config.archs = new ArrayList<>();
}
config.archs.clear();
config.archs.addAll(archs);
// initialization of sliceArch is needed for IBXcodeProjects where build() is not invoked
config.sliceArch = config.archs.isEmpty() ? null : config.archs.get(0);
return this;
}
public Builder clearClasspathEntries() {
if (config.classpath != null) {
config.classpath.clear();
}
return this;
}
public Builder addClasspathEntry(File f) {
if (config.classpath == null) {
config.classpath = new ClasspathentryList();
}
config.classpath.add(f);
return this;
}
public Builder clearBootClasspathEntries() {
if (config.bootclasspath != null) {
config.bootclasspath.clear();
}
return this;
}
public Builder addBootClasspathEntry(File f) {
if (config.bootclasspath == null) {
config.bootclasspath = new ClasspathentryList();
}
config.bootclasspath.add(f);
return this;
}
public Builder mainJar(File f) {
config.mainJar = f;
return this;
}
public Builder installDir(File installDir) {
config.installDir = installDir;
return this;
}
public Builder executableName(String executableName) {
config.executableName = executableName;
return this;
}
public Builder imageName(String imageName) {
config.imageName = imageName;
return this;
}
public Builder home(Home home) {
config.home = home;
return this;
}
public Builder cacheDir(File cacheDir) {
config.cacheDir = cacheDir;
return this;
}
public Builder clean(boolean b) {
config.clean = b;
return this;
}
public Builder ccBinPath(File ccBinPath) {
config.ccBinPath = ccBinPath;
return this;
}
public Builder debug(boolean b) {
config.debug = b;
return this;
}
public Builder useDebugLibs(boolean b) {
config.useDebugLibs = b;
return this;
}
public Builder dumpIntermediates(boolean b) {
config.dumpIntermediates = b;
return this;
}
public Builder manuallyPreparedForLaunch(boolean b) {
config.manuallyPreparedForLaunch = b;
return this;
}
public Builder skipRuntimeLib(boolean b) {
config.skipRuntimeLib = b;
return this;
}
public Builder skipLinking(boolean b) {
config.skipLinking = b;
return this;
}
public Builder skipInstall(boolean b) {
config.skipInstall = b;
return this;
}
public Builder threads(int threads) {
config.threads = threads;
return this;
}
public Builder mainClass(String mainClass) {
config.mainClass = mainClass;
return this;
}
public Builder tmpDir(File tmpDir) {
config.tmpDir = tmpDir;
return this;
}
public Builder logger(Logger logger) {
config.logger = logger;
return this;
}
public Builder treeShakerMode(TreeShakerMode treeShakerMode) {
config.treeShakerMode = treeShakerMode;
return this;
}
public Builder smartSkipRebuild(boolean smartSkipRebuild){
config.smartSkipRebuild = smartSkipRebuild;
return this;
}
public Builder clearForceLinkClasses() {
if (config.forceLinkClasses != null) {
config.forceLinkClasses.clear();
}
return this;
}
public Builder addForceLinkClass(String pattern) {
if (config.forceLinkClasses == null) {
config.forceLinkClasses = new ForceLinkClassesList();
}
config.forceLinkClasses.add(pattern);
return this;
}
public Builder clearExportedSymbols() {
if (config.exportedSymbols != null) {
config.exportedSymbols.clear();
}
return this;
}
public Builder addExportedSymbol(String symbol) {
if (config.exportedSymbols == null) {
config.exportedSymbols = new SymbolsList();
}
config.exportedSymbols.add(symbol);
return this;
}
public Builder clearUnhideSymbols() {
if (config.unhideSymbols != null) {
config.unhideSymbols.clear();
}
return this;
}
public Builder addUnhideSymbol(String symbol) {
if (config.unhideSymbols == null) {
config.unhideSymbols = new SymbolsList();
}
config.unhideSymbols.add(symbol);
return this;
}
public Builder clearLibs() {
if (config.libs != null) {
config.libs.clear();
}
return this;
}
public Builder addLib(Lib lib) {
if (config.libs == null) {
config.libs = new LibsList();
}
config.libs.add(lib);
return this;
}
public Builder clearFrameworks() {
if (config.frameworks != null) {
config.frameworks.clear();
}
return this;
}
public Builder addFramework(String framework) {
if (config.frameworks == null) {
config.frameworks = new FrameworksList();
}
config.frameworks.add(new QualifiedEntry(framework));
return this;
}
public Builder addXCFramework(File xcFramework) {
if (config.xcFrameworks == null) {
config.xcFrameworks = new PathsList();
}
config.xcFrameworks.add(new QualifiedFile(xcFramework));
return this;
}
public Builder clearWeakFrameworks() {
if (config.weakFrameworks != null) {
config.weakFrameworks.clear();
}
return this;
}
public Builder addWeakFramework(String framework) {
if (config.weakFrameworks == null) {
config.weakFrameworks = new FrameworksList();
}
config.weakFrameworks.add(new QualifiedEntry(framework));
return this;
}
public Builder clearFrameworkPaths() {
if (config.frameworkPaths != null) {
config.frameworkPaths.clear();
}
return this;
}
public Builder addFrameworkPath(File frameworkPath) {
if (config.frameworkPaths == null) {
config.frameworkPaths = new PathsList();
}
config.frameworkPaths.add(new QualifiedFile(frameworkPath));
return this;
}
public Builder clearExtensions() {
if (config.appExtensions != null) {
config.appExtensions.clear();
}
return this;
}
public Builder addExtension(String name, String profile) {
if (config.appExtensions == null) {
config.appExtensions = new AppExtensionsList();
}
AppExtension extension = new AppExtension();
extension.name = name;
extension.profile = profile;
config.appExtensions.add(extension);
return this;
}
public Builder clearExtensionPaths() {
if (config.appExtensionPaths != null) {
config.appExtensionPaths.clear();
}
return this;
}
public Builder addExtenaionPath(File extensionPath) {
if (config.appExtensionPaths == null) {
config.appExtensionPaths = new PathsList();
}
config.appExtensionPaths.add(new QualifiedFile(extensionPath));
return this;
}
public Builder clearResources() {
if (config.resources != null) {
config.resources.clear();
}
return this;
}
public Builder addResource(Resource resource) {
if (config.resources == null) {
config.resources = new ResourcesList();
}
config.resources.add(resource);
return this;
}
public Builder stripArchivesBuilder(StripArchivesBuilder stripArchivesBuilder) {
this.config.stripArchivesBuilder = stripArchivesBuilder;
return this;
}
public Builder targetType(String targetType) {
config.targetType = targetType;
return this;
}
public Builder clearProperties() {
config.properties.clear();
return this;
}
public Builder addProperties(Properties properties) {
config.properties.putAll(properties);
return this;
}
public Builder addProperties(File file) throws IOException {
Properties props = new Properties();
try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
props.load(reader);
addProperties(props);
}
return this;
}
public Builder addProperty(String name, String value) {
config.properties.put(name, value);
return this;
}
public Builder cacerts(Cacerts cacerts) {
config.cacerts = cacerts;
return this;
}
public Builder tools(Tools tools) {
config.tools = tools;
return this;
}
public Builder iosSdkVersion(String sdkVersion) {
config.iosSdkVersion = sdkVersion;
return this;
}
public Builder iosDeviceType(String deviceType) {
config.iosDeviceType = deviceType;
return this;
}
public Builder iosInfoPList(File infoPList) {
config.iosInfoPListFile = infoPList;
return this;
}
public Builder infoPList(File infoPList) {
config.infoPListFile = infoPList;
return this;
}
public Builder iosEntitlementsPList(File entitlementsPList) {
config.iosEntitlementsPList = entitlementsPList;
return this;
}
public Builder iosSignIdentity(SigningIdentity signIdentity) {
config.iosSignIdentity = signIdentity;
return this;
}
public Builder iosProvisioningProfile(ProvisioningProfile iosProvisioningProfile) {
config.iosProvisioningProfile = iosProvisioningProfile;
return this;
}
public Builder iosSkipSigning(boolean b) {
config.iosSkipSigning = b;
return this;
}
public Builder addCompilerPlugin(CompilerPlugin compilerPlugin) {
config.plugins.add(compilerPlugin);
return this;
}
public Builder addLaunchPlugin(LaunchPlugin plugin) {
config.plugins.add(plugin);
return this;
}
public Builder addTargetPlugin(TargetPlugin plugin) {
config.plugins.add(plugin);
return this;
}
public Builder enableBitcode(boolean enableBitcode) {
config.enableBitcode = enableBitcode;
return this;
}
public void addPluginArgument(String argName) {
if (config.pluginArguments == null) {
config.pluginArguments = new PluginArgumentsList();
}
config.pluginArguments.add(argName);
}
public Config build() throws IOException {
for (CompilerPlugin plugin : config.getCompilerPlugins()) {
plugin.beforeConfig(this, config);
}
return config.build();
}
/**
* Reads properties from a project basedir. If {@code isTest} is
* {@code true} this method will first attempt to load a
* {@code robovm.test.properties} file in {@code basedir}.
*
* If no test specific file is found or if {@code isTest} is
* {@code false} this method attempts to load a
* {@code robovm.properties} and a {@code robovm.local.properties} file
* in {@code basedir} and merges them so that properties from the local
* file (if it exists) override properties in the non-local file.
*
* If {@code isTest} is {@code true} and no test specific properties
* file was found this method will append {@code Test} to the
* {@code app.id} and {@code app.name} properties (if they exist).
*
* If none of the files can be found found this method does nothing.
*/
public void readProjectProperties(File basedir, boolean isTest) throws IOException {
File testPropsFile = new File(basedir, "robovm.test.properties");
File localPropsFile = new File(basedir, "robovm.local.properties");
File propsFile = new File(basedir, "robovm.properties");
if (isTest && testPropsFile.exists()) {
config.logger.info("Loading test RoboVM config properties file: "
+ testPropsFile.getAbsolutePath());
addProperties(testPropsFile);
} else {
Properties props = new Properties();
if (propsFile.exists()) {
config.logger.info("Loading default RoboVM config properties file: "
+ propsFile.getAbsolutePath());
try (Reader reader = new InputStreamReader(new FileInputStream(propsFile), StandardCharsets.UTF_8)) {
props.load(reader);
}
}
if (localPropsFile.exists()) {
config.logger.info("Loading local RoboVM config properties file: "
+ localPropsFile.getAbsolutePath());
try (Reader reader = new InputStreamReader(new FileInputStream(localPropsFile), StandardCharsets.UTF_8)) {
props.load(reader);
}
}
if (isTest) {
modifyPropertyForTest(props, "app.id");
modifyPropertyForTest(props, "app.name");
modifyPropertyForTest(props, "app.executable");
}
addProperties(props);
}
}
private void modifyPropertyForTest(Properties props, String propName) {
String propValue = props.getProperty(propName);
if (propValue != null && !propValue.endsWith("Test")) {
String newPropValue = propValue + "Test";
config.logger.info("Changing %s property from '%s' to '%s'", propName, propValue, newPropValue);
props.setProperty(propName, newPropValue);
}
}
/**
* Reads a config file from a project basedir. If {@code isTest} is
* {@code true} this method will first attempt to load a
* {@code robovm.test.xml} file in {@code basedir}.
*
* If no test-specific file is found or if {@code isTest} is
* {@code false} this method attempts to load a {@code robovm.xml} file
* in {@code basedir}.
*
* If none of the files can be found found this method does nothing.
*/
public void readProjectConfig(File basedir, boolean isTest) throws IOException {
File testConfigFile = new File(basedir, "robovm.test.xml");
File configFile = new File(basedir, "robovm.xml");
if (isTest && testConfigFile.exists()) {
config.logger.info("Loading test RoboVM config file: "
+ testConfigFile.getAbsolutePath());
read(testConfigFile);
} else if (configFile.exists()) {
config.logger.info("Loading default RoboVM config file: "
+ configFile.getAbsolutePath());
read(configFile);
}
}
public void read(File file) throws IOException {
try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
read(reader, file.getAbsoluteFile().getParentFile());
}
}
public void read(Reader reader, File wd) throws IOException {
try {
Serializer serializer = createSerializer(config, wd);
serializer.read(config, reader);
} catch (IOException | RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IOException(e.getLocalizedMessage(), e);
}
// was renamed to but we still support
// . We need to copy to and set
// to null.
if (config.roots != null && !config.roots.isEmpty()) {
if (config.forceLinkClasses == null) {
config.forceLinkClasses = new ForceLinkClassesList();
}
config.forceLinkClasses.addAll(config.roots);
config.roots = null;
}
}
public void write(File file) throws IOException {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
write(writer, file.getAbsoluteFile().getParentFile());
}
}
public void write(Writer writer, File wd) throws IOException {
try {
Serializer serializer = createSerializer(config, wd);
serializer.write(config, writer);
} catch (IOException | RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
}
public static Serializer createSerializer(Config config, final File wd) throws Exception {
RelativeFileConverter fileConverter = new RelativeFileConverter(wd);
Serializer resourceSerializer = new Persister(
new RegistryStrategy(new Registry().bind(File.class, fileConverter)),
new PlatformFilter(config.properties), new Format(2));
Registry registry = new Registry();
RegistryStrategy registryStrategy = new RegistryStrategy(registry);
RegistryMatcher matcher = new RegistryMatcher();
Serializer serializer = new Persister(registryStrategy,
new PlatformFilter(config.properties), matcher, new Format(2));
registry.bind(File.class, fileConverter);
registry.bind(Resource.class, new ResourceConverter(fileConverter, resourceSerializer));
registry.bind(StripArchivesConfig.class, new StripArchivesConfigConverter());
// converters for attributes (comma separated arrays)
// adding file converter to matcher, as it fails to pick it from registry when writing
// tag text of custom object (such as QualifiedFile)
matcher.bind(File.class, fileConverter);
matcher.bind(Arch.class, new ArchTransformer());
matcher.bind(Lib.PathWrap.class, new RelativeLibPathTransformer(fileConverter));
matcher.bind(OS[].class, new EnumArrayConverter<>(OS.class));
matcher.bind(Arch[].class, new ArchArrayConverter());
matcher.bind(PlatformVariant[].class, new EnumArrayConverter<>(PlatformVariant.class));
return serializer;
}
/**
* Fetches the {@link PluginArgument}s of all registered plugins for
* parsing.
*/
public Map fetchPluginArguments() {
Map args = new TreeMap<>();
for (Plugin plugin : config.plugins) {
for (PluginArgument arg : plugin.getArguments().getArguments()) {
args.put(plugin.getArguments().getPrefix() + ":" + arg.getName(), arg);
}
}
return args;
}
public List getPlugins() {
return config.getPlugins();
}
}
public static final class Lib extends AbstractQualified {
// using here special type PathWrap. Purpose is to use RelativeLibPathTransformer
// that will apply relative path transformation.
static final class PathWrap {
String value;
PathWrap(String v) { value = v; }
}
@Text PathWrap pathWrap;
// force was made nullable by purpose: it's expected by UT to be missing in XML
// if it contains default value "true". it's possible to remove attribute only
// by declaring it as "required = false" and nulling in case of true.
// before serialization, any true should go into null, this is covered in
// @Persist annotated method
@Attribute(name = "force", required = false) Boolean force;
@Persist
private void nullForceValueBeforeSerialization() {
if (force != null && force)
force = null;
}
// default constructor is required for serializer
protected Lib() {}
public Lib(String value, boolean force) {
this.pathWrap = new PathWrap(value);
this.force = force;
}
public Lib(String value, boolean force, OS[] platforms, PlatformVariant[] variants, Arch[] arches) {
this.pathWrap = new PathWrap(value);
this.force = force;
this.platforms = platforms;
this.variants = variants;
this.arches = arches;
}
public String getValue() {
return pathWrap != null ? pathWrap.value : null;
}
public boolean isForce() {
return force == null || force;
}
@Override
public String toString() {
return "Lib [value=" + getValue() + ", force=" + force + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (force ? 1231 : 1237);
result = prime * result + ((pathWrap == null) ? 0 : pathWrap.value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Lib other = (Lib) obj;
if (isForce() != other.isForce()) {
return false;
}
if (pathWrap == null) {
return other.pathWrap == null;
} else return pathWrap.value.equals(other.pathWrap.value);
}
}
/**
* Container for text entry with platform/arch constraints
*/
public static final class QualifiedEntry extends AbstractQualified {
@Text String entry;
protected QualifiedEntry() {
}
public QualifiedEntry(String entry) {
this.entry = entry;
}
public String getEntry() {
return entry;
}
@Override
public String toString() {
return entry + " " + super.toString();
}
}
/**
* Container for file entry with platform/arch constraints
*/
public static final class QualifiedFile extends AbstractQualified {
@Text File entry;
protected QualifiedFile() {
}
public QualifiedFile(File file) {
entry = file;
}
public File getEntry() {
return entry;
}
@Override
public String toString() {
return entry + " " + super.toString();
}
}
private static final class RelativeLibPathTransformer implements Transform {
private final RelativeFileConverter fileConverter;
public RelativeLibPathTransformer(RelativeFileConverter fileConverter) {
this.fileConverter = fileConverter;
}
@Override
public Lib.PathWrap read(String value) throws Exception {
if (value == null) {
return null;
}
if (value.endsWith(".a") || value.endsWith(".o")) {
value = fileConverter.read(value).getAbsolutePath();
} else if (value.endsWith(".dylib") || value.endsWith(".so")) {
File f = fileConverter.read(value);
if (f.isFile())
value = f.getAbsolutePath();
}
return new Lib.PathWrap(value);
}
@Override
public String write(Lib.PathWrap wrap) throws Exception {
if (wrap != null) {
String value = wrap.value;
if (value.endsWith(".a") || value.endsWith(".o")) {
return fileConverter.write(new File(value));
} else {
return value;
}
} else return null;
}
}
private static final class ArchTransformer implements Transform {
@Override
public Arch read(String value) throws Exception {
if (value == null) {
return null;
}
return Arch.parse(value);
}
@Override
public String write(Arch s) throws Exception {
if (s != null) {
return s.toString();
} else return null;
}
}
private static final class ArchArrayConverter implements Transform {
@Override
public Arch[] read(String s) {
s = s.trim();
if (s.isEmpty())
return null;
String[] tokens = s.split(",");
Arch[] res = new Arch[tokens.length];
for (int idx = 0; idx < tokens.length; idx++)
res[idx] = Arch.parse(tokens[idx].trim());
return res;
}
@Override
public String write(Arch[] ts) {
return Arrays.stream(ts).map(Arch::toString).collect(Collectors.joining());
}
}
/**
* transformer for xml attribute/entry that transforms comma separated values into
* enum array
*/
private static final class EnumArrayConverter> implements Transform {
private final Class enumClass;
private EnumArrayConverter(Class enumClass) {
this.enumClass = enumClass;
}
@Override
public T[] read(String s) {
s = s.trim();
if (s.isEmpty())
return null;
String[] tokens = s.split(",");
@SuppressWarnings("unchecked")
T[] res = (T[])Array.newInstance(enumClass, tokens.length);
for (int idx = 0; idx < tokens.length; idx++)
res[idx] = Enum.valueOf(enumClass, tokens[idx].trim());
return res;
}
@Override
public String write(T[] ts) {
return Arrays.stream(ts).map(Enum::name).collect(Collectors.joining());
}
}
private static final class RelativeFileConverter implements Converter, Transform {
private final String wdPrefix;
public RelativeFileConverter(File wd) {
if (wd.isFile()) {
wd = wd.getParentFile();
}
String prefix = wd.getAbsolutePath();
if (prefix.endsWith(File.separator)) {
prefix = prefix.substring(0, prefix.length() - 1);
}
wdPrefix = prefix;
}
@Override
public File read(String value) {
if (value == null) {
return null;
}
File file = new File(value);
if (!file.isAbsolute()) {
file = new File(wdPrefix, value);
}
return file;
}
@Override
public File read(InputNode node) throws Exception {
return read(node.getValue());
}
@Override
public String write(File value) {
String path = value.isAbsolute() ? value.getAbsolutePath() : value.getPath();
if (value.isAbsolute() && path.startsWith(wdPrefix)) {
if (path.length() == wdPrefix.length())
path = "";
else
path = path.substring(wdPrefix.length() + 1);
}
return path;
}
@Override
public void write(OutputNode node, File value) throws Exception {
String path = write(value);
if (path.isEmpty()) {
if ("directory".equals(node.getName())) {
// Skip
node.remove();
} else {
node.setValue("");
}
} else {
node.setValue(path);
}
}
}
private static final class ResourceConverter implements Converter {
private final RelativeFileConverter fileConverter;
private final Serializer serializer;
public ResourceConverter(RelativeFileConverter fileConverter, Serializer serializer) {
this.fileConverter = fileConverter;
this.serializer = serializer;
}
@Override
public Resource read(InputNode node) throws Exception {
String value = node.getValue();
if (value != null && value.trim().length() > 0) {
return new Resource(fileConverter.read(value));
}
return serializer.read(Resource.class, node);
}
@Override
public void write(OutputNode node, Resource resource) throws Exception {
File path = resource.getPath();
if (path != null) {
fileConverter.write(node, path);
} else {
node.remove();
serializer.write(resource, node.getParent());
}
}
}
private static final class StripArchivesConfigConverter implements Converter {
@Override
public StripArchivesConfig read(InputNode node) throws Exception {
StripArchivesBuilder cfgBuilder = new StripArchivesBuilder();
InputNode childNode;
while ((childNode = node.getNext()) != null) {
if (childNode.isElement() && !childNode.isEmpty() && childNode.getName().equals("include") || childNode.getName().equals("exclude")) {
boolean isInclude = childNode.getName().equals("include");
cfgBuilder.add(isInclude, childNode.getValue());
}
}
return cfgBuilder.build();
}
@Override
public void write(OutputNode node, StripArchivesConfig config) throws Exception {
if (config.getPatterns() != null && !config.getPatterns().isEmpty()) {
for (StripArchivesConfig.Pattern pattern : config.getPatterns()) {
OutputNode child = node.getChild(pattern.isInclude() ? "include" : "exclude");
child.setValue(pattern.getPatternAsString());
child.commit();
}
}
node.commit();
}
}
private ResolvedLocations resolveLocations() {
ResolvedLocations.Resolver resolver = new ResolvedLocations.Resolver(os, sliceArch);
resolver.setFrameworks(frameworks)
.setWeakFrameworks(weakFrameworks)
.setFrameworkPaths(frameworkPaths)
.setLibs(libs)
.setXcFrameworkLookup(experimental.isXCFrameworksEnabled())
.setQualifier(this::isQualified)
.setXcFrameworks(xcFrameworks);
return resolver.resolve();
}
}