All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robovm.compiler.target.AbstractTarget Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 RoboVM AB
 *
 * 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.target;

import com.dd.plist.NSDictionary;
import com.dd.plist.PropertyListParser;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.robovm.compiler.Version;
import org.robovm.compiler.clazz.Path;
import org.robovm.compiler.config.*;
import org.robovm.compiler.config.Resource.Walker;
import org.robovm.compiler.target.ios.IOSTarget;
import org.robovm.compiler.util.ToolchainUtil;
import org.simpleframework.xml.Transient;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * @author niklas
 *
 */
public abstract class AbstractTarget implements Target {
    @Transient
    protected Config config;

    protected AbstractTarget() {
    }

    @Override
    public void init(Config config) {
        this.config = config;
    }

    @Override
    public boolean canLaunch() {
        return true;
    }

    @Override
    public void prepareLaunch() throws IOException {
    }

    @Override
    public LaunchParameters createLaunchParameters() {
        return new LaunchParameters();
    }

    public String getInstallRelativeArchivePath(Path path) {
        String name = config.getArchiveName(path);
        if (path.isInBootClasspath()) {
            return "lib" + File.separator + "boot" + File.separator + name;
        }
        return "lib" + File.separator + name;
    }

    public boolean canLaunchInPlace() {
        return true;
    }

    protected List getTargetExportedSymbols() {
        return Collections.emptyList();
    }

    protected List getTargetCcArgs() {
        return Collections.emptyList();
    }

    protected List getTargetLibs() {
        return Collections.emptyList();
    }

    public File build(List objectFiles) throws IOException {
        File outFile = new File(config.getTmpDir(), config.getExecutableName());

        config.getLogger().info("Building %s binary %s", config.getTarget().getType(), outFile);

        LinkedList ccArgs = new LinkedList();
        LinkedList libs = new LinkedList();

        ccArgs.addAll(getTargetCcArgs());
        libs.addAll(getTargetLibs());

        String libSuffix = config.isUseDebugLibs() ? "-dbg" : "";

        libs.add("-lrobovm-bc" + libSuffix);
        if (config.getOs().getFamily() == OS.Family.darwin) {
            libs.add("-force_load");
            libs.add(new File(config.getOsArchDepLibDir(), "librobovm-rt" + libSuffix + ".a").getAbsolutePath());
        } else {
            libs.addAll(Arrays.asList("-Wl,--whole-archive", "-lrobovm-rt" + libSuffix, "-Wl,--no-whole-archive"));
        }
        if (config.isSkipInstall()) {
            libs.add("-lrobovm-debug" + libSuffix);
        }
        libs.addAll(Arrays.asList(
                "-lrobovm-core" + libSuffix, "-lgc" + libSuffix, "-lpthread", "-ldl", "-lm", "-lz"));
        if (config.getOs().getFamily() == OS.Family.linux) {
            libs.add("-lrt");
        }
        if (config.getOs().getFamily() == OS.Family.darwin) {
            libs.add("-liconv");
            libs.add("-lsqlite3");
            libs.add("-framework");
            libs.add("Foundation");
        }

        ccArgs.add("-L");
        ccArgs.add(config.getOsArchDepLibDir().getAbsolutePath());

        List exportedSymbols = new ArrayList();
        exportedSymbols.addAll(getTargetExportedSymbols());
        exportedSymbols.add("JNI_OnLoad_*");
        exportedSymbols.addAll(config.getExportedSymbols());

        if (config.getOs().getFamily() == OS.Family.linux) {
            ccArgs.add("-Wl,-rpath=$ORIGIN");
            ccArgs.add("-Wl,--gc-sections");
//            ccArgs.add("-Wl,--print-gc-sections");

            if (!exportedSymbols.isEmpty()) {
                // Create an ld version script which makes the exported symbols global
                // and all other symbols local.
                StringBuilder sb = new StringBuilder();
                sb.append("{\n    ");
                sb.append(StringUtils.join(exportedSymbols, ";\n    "));
                sb.append(";\n};\n");

                File dynamicListFile = new File(config.getTmpDir(), "exported_symbols");
                FileUtils.writeStringToFile(dynamicListFile, sb.toString());
                ccArgs.add("-Wl,--dynamic-list=" + dynamicListFile.getAbsolutePath());
            }

        } else if (config.getOs().getFamily() == OS.Family.darwin) {
            ccArgs.add("-ObjC");

            if (config.isSkipInstall()) {
                exportedSymbols.add("catch_exception_raise");
            }
            for (int i = 0; i < exportedSymbols.size(); i++) {
                // On Darwin symbols are always prefixed with a '_'. We'll prepend
                // '_' to each symbol here so the user won't have to.
                // (excluding * starting wildcards)
                String symb = exportedSymbols.get(i);
                exportedSymbols.set(i, symb.startsWith("*") ? symb : "_" + symb);
            }

            if (!config.getUnhideSymbols().isEmpty()) {
                List aliasedSymbols = new ArrayList();
                for (String symbol : config.getUnhideSymbols()) {
                    aliasedSymbols.add("_" + symbol + " __unhidden_" + symbol);
                }
                File aliasedSymbolsFile = new File(config.getTmpDir(), "aliased_symbols");
                FileUtils.writeLines(aliasedSymbolsFile, "ASCII", aliasedSymbols);
                ccArgs.add("-Xlinker");
                ccArgs.add("-alias_list");
                ccArgs.add("-Xlinker");
                ccArgs.add(aliasedSymbolsFile.getAbsolutePath());
                exportedSymbols.add("__unhidden_*");
            }

            File exportedSymbolsFile = new File(config.getTmpDir(), "exported_symbols");
            FileUtils.writeLines(exportedSymbolsFile, "ASCII", exportedSymbols);
            ccArgs.add("-exported_symbols_list");
            ccArgs.add(exportedSymbolsFile.getAbsolutePath());

            ccArgs.add("-Wl,-no_implicit_dylibs");
            ccArgs.add("-Wl,-dead_strip");
        }

        if (config.getOs().getFamily() == OS.Family.darwin && !config.getFrameworks().isEmpty()) {
            for (String p : config.getFrameworks()) {
                libs.add("-framework");
                libs.add(p);
            }
        }
        if (config.getOs().getFamily() == OS.Family.darwin && !config.getWeakFrameworks().isEmpty()) {
            for (String p : config.getWeakFrameworks()) {
                libs.add("-weak_framework");
                libs.add(p);
            }
        }
        if (config.getOs().getFamily() == OS.Family.darwin && !config.getFrameworkPaths().isEmpty()) {
            for (File p : config.getFrameworkPaths()) {
                ccArgs.add("-F" + p.getAbsolutePath());
            }
        }

        if (config.hasSwiftSupport()) {
            // add swift lib locations to library search path
            File[] swiftLibLocations = getSwiftDirs(config);
            for (File dir : swiftLibLocations)
                ccArgs.add("-L" + dir.getAbsolutePath());
        }

        if (!config.getLibs().isEmpty()) {
            objectFiles = new ArrayList(objectFiles);
            for (Config.Lib lib : config.getLibs()) {
                String p = lib.getValue();
                if (p.endsWith(".o")) {
                    objectFiles.add(new File(p));
                } else if (p.endsWith(".a")) {
                    // .a file
                    if (config.getOs().getFamily() == OS.Family.darwin) {
                        if (lib.isForce()) {
                            libs.add("-force_load");
                        }
                        libs.add(new File(p).getAbsolutePath());
                    } else {
                        if (lib.isForce()) {
                            libs.add("-Wl,--whole-archive");
                        }
                        libs.add(new File(p).getAbsolutePath());
                        if (lib.isForce()) {
                            libs.add("-Wl,--no-whole-archive");
                        }
                    }
                } else if (p.endsWith(".dylib") || p.endsWith(".so")) {
                    // dkimitsa: add absolute path only if Config.Lib relative file converter was able to resolve it
                    //           e.g. lib exists, otherwise use it as it is
                    File f = new File(p);
                    libs.add(f.isAbsolute() ? f.getAbsolutePath() : p);
                } else {
                    // link via -l if suffix is omitted
                    libs.add("-l" + p);
                }
            }
        }

        ccArgs.add("-fPIC");
        if (config.getOs() == OS.macosx) {
            if (!config.getFrameworks().contains("CoreServices")) {
                libs.add("-framework");
                libs.add("CoreServices");
            }
        } else if (config.getOs() == OS.ios) {
            if (!config.getFrameworks().contains("MobileCoreServices")) {
                libs.add("-framework");
                libs.add("MobileCoreServices");
            }
        }

        if (config.getTools() != null && config.getTools().getLinker() != null) {
            ccArgs.addAll(config.getTools().getLinker().getLinkerFlags());
        }

        doBuild(outFile, ccArgs, objectFiles, libs);
        return outFile;
    }

    protected void doBuild(File outFile, List ccArgs, List objectFiles,
            List libs) throws IOException {

        ToolchainUtil.link(config, ccArgs, objectFiles, libs, outFile);
    }

    protected File getAppDir() {
        if (!config.isSkipInstall()) {
            return config.getInstallDir();
        } else {
            return config.getTmpDir();
        }
    }

    protected String getExecutable() {
        return config.getExecutableName();
    }

    protected String getBundleId() {
        return config.getMainClass() != null ? config.getMainClass() : config.getExecutableName();
    }

    @Override
    public void buildFat(Map slices) throws IOException {
        File destFile = new File(config.getTmpDir(), getExecutable());
        List files = new ArrayList<>(slices.values());
        if (slices.size() > 1) {
            if (config.getOs() == OS.linux) {
                throw new UnsupportedOperationException("Fat binaries are not supported when building linux binaries");
            }
            config.getLogger().info("Building fat binary for archs %s", StringUtils.join(slices.keySet()));
            ToolchainUtil.lipo(config, destFile, files);
        } else if (!files.get(0).equals(destFile)) {
            FileUtils.copyFile(files.get(0), destFile);
            destFile.setExecutable(true, false);
        }
    }

    protected void copyResources(File destDir) throws IOException {
        Walker walker = new Walker() {
            @Override
            public boolean processDir(Resource resource, File dir, File destDir) throws IOException {
                return true;
            }

            @Override
            public void processFile(Resource resource, File file, File destDir)
                    throws IOException {

                copyFile(resource, file, destDir);
            }
        };

        for (Resource res : config.getResources()) {
            res.walk(walker, destDir);
        }
    }

    protected void copyDynamicFrameworks(File destDir, File appExecutable) throws IOException {
        final Set swiftLibraries = new HashSet<>();
        final Set weakSwiftLibraries = new HashSet<>();
        File frameworksDir = new File(destDir, "Frameworks");

        for (String framework : config.getFrameworks()) {
            boolean isCustomFramework = false;
            File frameworkDir = null;
            for (File path : config.getFrameworkPaths()) {
                frameworkDir = new File(path, framework + ".framework");
                if (frameworkDir.exists() && frameworkDir.isDirectory()) {
                    isCustomFramework = true;
                    break;
                }
            }

            if (isCustomFramework) {
                // check if this is a dynamic framework by finding
                // at least ony dylib in the root folder
                boolean isDynamicFramework = false;
                for(File file: frameworkDir.listFiles()) {
                    if(file.isFile() && isDynamicLibrary(file)) {
                        isDynamicFramework = true;
                        break;
                    }
                }

                if(isDynamicFramework) {
                    config.getLogger().info("Copying framework %s from %s to %s", framework, frameworkDir, destDir);
                    new Resource(frameworkDir).walk(new Walker() {
                        @Override
                        public boolean processDir(Resource resource, File dir, File destDir) throws IOException {
                            return !(dir.getName().equals("Headers") || dir.getName().equals("PrivateHeaders")
                                    || dir.getName().equals("Modules") || dir.getName().equals("Versions") || dir.getName()
                                    .equals("Documentation"));
                        }

                        @Override
                        public void processFile(Resource resource, File file, File destDir) throws IOException {
                            if (!isStaticLibrary(file)) {
                                copyFile(resource, file, destDir);

                                if (isDynamicLibrary(file)) {
                                    // remove simulator and deprecated archs, also strip bitcode if not used
                                    if (config.getOs() == OS.ios && config.getArch().getEnv() != Environment.Simulator) {
                                        File libFile = new File(destDir, file.getName());
                                        stripExtraArches(libFile);
                                        if (!config.isEnableBitcode())
                                            stripBitcode(libFile);
                                    }

                                    // check if this dylib depends on Swift
                                    // and register those libraries to be copied
                                    // to bundle.app/Frameworks
                                    getSwiftDependencies(file, swiftLibraries, weakSwiftLibraries);
                                }
                            }
                        }

                    }, frameworksDir);
                }
            }
        }

        if (config.hasSwiftSupport() && config.getSwiftSupport().shouldCopySwiftLibs()) {
            // find swift libraries that might be referenced in executable due static linking
            getSwiftDependencies(appExecutable, swiftLibraries, weakSwiftLibraries);

            // workaround: check if libs contain reference to swift lib
            // if project links against static swift library it requires
            // dynamic swift libraries to be included. and these are not automatically
            // resolved yet, allow user to specify them in library list
            for (Config.Lib lib : config.getLibs()) {
                String p = lib.getValue();
                if (p.startsWith("libswift") && p.endsWith(".dylib") && !new File(p).exists()) {
                    swiftLibraries.add(p);
                    if (!lib.isForce()) weakSwiftLibraries.add(p);
                }
            }

            // copy Swift libraries if required
            if (!swiftLibraries.isEmpty()) {
                copySwiftLibs(swiftLibraries, weakSwiftLibraries, frameworksDir, true);
            }
        }
    }

    protected void getSwiftDependencies(File file, Collection swiftLibraries, Collection weakSwiftLibraries) throws IOException {
        String dependencies = ToolchainUtil.otool(file);
        // dependency string example
        // @rpath/libswiftXPC.dylib (compatibility version 1.0.0, current version 36.100.7, weak)
        Pattern swiftLibraryPattern = Pattern.compile("@rpath/(libswift.+\\.dylib)(?:.*(weak))?");
        Matcher matcher = swiftLibraryPattern.matcher(dependencies);
        while (matcher.find()) {
            String library = matcher.group(1);
            swiftLibraries.add(library);
            if (matcher.groupCount() > 1) {
                // `weak` was present, consider library for weak linking
                weakSwiftLibraries.add(library);
            }
        }
    }

    protected void copyAppExtensions(File destDir) throws IOException {
        File pluginsDir = new File(destDir, "PlugIns");

        List extPaths = config.getAppExtensionPaths();
        String appBundleId = getBundleId();
        for (AppExtension extensionVo : config.getAppExtensions()) {
            String extensionName = extensionVo.getNameWithExt(".appex");
            File extensionDir = findDirectoryInLocations(extensionName, extPaths);
            if (extensionDir == null)
                throw new IllegalArgumentException(String.format("Specified app extension `%s` not found in ext paths", extensionVo.getName()));

            config.getLogger().info("Copying app-extension %s from %s to %s", extensionName, extensionDir, pluginsDir);
            new Resource(extensionDir).walk(new Walker() {
                @Override
                public boolean processDir(Resource resource, File dir, File destDir) throws IOException {
                    return true;
                }

                @Override
                public void processFile(Resource resource, File file, File destDir) throws IOException {
                    copyFile(resource, file, destDir);

                    if (config.getOs() == OS.ios && config.getArch().getEnv() != Environment.Simulator) {
                        // remove simulator and deprecated archs, also strip bitcode if not used
                        if (isAppExtension(file)) {
                            File libFile = new File(destDir, file.getName());
                            stripExtraArches(libFile);
                            if (!config.isEnableBitcode())
                                stripBitcode(libFile);
                        }
                    }
                }
            }, pluginsDir);

            // app extension shall extend app id, e.g. if app id = com.sample.app
            // all app extensions shall have "com.sample.app." as prefix.
            // just override all app extension ids by adding extension name to app id
            // read app ext info.plist
            updateAppExtensionBundleId(new File(pluginsDir, extensionName), extensionVo, appBundleId);
        }
    }

    /**
     * finds directory in specified location list
     * @return path to directory or null if not found
     */
    protected File findDirectoryInLocations(String dirName, List locations) {
        for (File path : locations) {
            File extPath = new File(path, dirName);
            if (extPath.exists() && extPath.isDirectory())
                return extPath;
        }

        return null;
    }

    /**
     * updates application extension bundle id
     * @param extensionPath  directory of extension
     * @param config         config of app extension
     * @param parentBundleId bundle id of application (parent)
     * @return updated bundle ID
     */
    protected String updateAppExtensionBundleId(File extensionPath, AppExtension config, String parentBundleId) {
        // read existing bundle id from Info.plist
        NSDictionary infoPlist;
        String appExBundleId;
        File infoPlistFile = new File(extensionPath, "Info.plist");
        try {
            infoPlist = (NSDictionary) PropertyListParser.parse(infoPlistFile);
            appExBundleId = infoPlist.get("CFBundleIdentifier").toString();
        } catch (Exception e) {
            throw new RuntimeException("Failed to read bundle id of extension " + extensionPath);
        }

        if (config.getSuffix() == null) {
            // keep existing bundle id, just validate it matches parent pattern
            if (!appExBundleId.startsWith(parentBundleId + "."))
                throw new RuntimeException(String.format("AppExtension (%s) shall extend parent bundle(%s) id while skipSigning!",
                        extensionPath.getName(), parentBundleId));
        } else if (config.getSuffix() != null) {
            // update and write back
            appExBundleId = parentBundleId + "." + config.getSuffix();
            try {
                infoPlist = (NSDictionary) PropertyListParser.parse(infoPlistFile);
                infoPlist.put("CFBundleIdentifier", appExBundleId);
                PropertyListParser.saveAsXML(infoPlist, infoPlistFile);
            } catch (Exception e) {
                throw new RuntimeException("Failed to update bundle id of extension " + extensionPath);
            }
        }

        return appExBundleId;
    }

    /**
     * Copies watch app and its extensions
     * @param installDir application install dir
     */
    protected void copyWatchApp(File installDir) throws IOException {
        WatchKitApp waConfig = config.getWatchKitApp();
        if (waConfig == null)
            return;

        // just copy entire extensions
        String waName = waConfig.getWatchAppName();
        File waSrcDir = findDirectoryInLocations(waName, config.getAppExtensionPaths());
        if (waSrcDir == null)
            throw new IllegalArgumentException("WatchApp with name " + waName + " not found in app extension paths!");

        File waDestDir = new File(installDir, "Watch/" + waName);
        config.getLogger().info("Copying Watch App from %s to %s", waSrcDir, waDestDir);
        FileUtils.copyDirectory(waSrcDir, waDestDir);

        // update bundle id of application
        String waKitBundleId = updateAppExtensionBundleId(waDestDir, waConfig.getApp(), getBundleId());

        // update bundle ids of its extension
        Map extensionsMap = new HashMap<>();
        waConfig.getExtensions().forEach(e -> extensionsMap.put(e.getNameWithExt(".appex"), e));

        // move through all extensions in directory
        File pluginsDir = new File(waDestDir, "PlugIns");
        for (File extPath : pluginsDir.listFiles()) {
            if (!extPath.isDirectory() || !extPath.getName().endsWith(".appex")) {
                config.getLogger().info("Skipping not expected file/dir '%s' in PlugIns folder: %s",
                        extPath.getName(), pluginsDir.getAbsolutePath());
                continue;

            }

            String name = extPath.getName();
            AppExtension extension = extensionsMap.get(name);
            if (extension == null) {
                extension = AppExtension.DEFAULT_RULE;
                updateAppExtensionBundleId(extPath, extension, waKitBundleId);
            }
        }
    }

    private File locateSwiftLib(File[] swiftDirs, String swiftLib) {
        for (File swiftDir : swiftDirs) {
            File f = new File(swiftDir, swiftLib);
            if (f.exists())
                return f;
        }
        return null;
    }

    private File[] getSwiftDirs(Config config) throws IOException {
        List configPaths = config.getSwiftLibPaths();
        if (!configPaths.isEmpty()) {
            // swift lib locations are provided in config,
            // paths in swiftSupport have to be qualified, no need to extend them with platform suffix
            List candidates = new ArrayList<>(configPaths);
            return candidates.toArray(new File[0]);
        } else {
            return getDefaultSwiftDirs(config);
        }
    }

    private String getSwiftSystemName(Config config) {
        String system;
        if (config.getOs() == OS.ios) {
            if (IOSTarget.isDeviceArch(config.getArch())) {
                system = "iphoneos";
            } else {
                system = "iphonesimulator";
            }
        } else {
            system = "macos";
        }

        return system;
    }

    private File[] getDefaultSwiftDirs(Config config) throws IOException {
        String system = getSwiftSystemName(config);
        return getDefaultSwiftDirs(system);
    }

    private void validateAndAddSwiftDir(List dest, String xcodePath, String version, String system) {
        File candidate = new File(xcodePath, "Toolchains/XcodeDefault.xctoolchain/usr/lib/" + version + "/" + system);
        if (candidate.exists() && candidate.isDirectory())
            dest.add(candidate);
    }

    private File[] getDefaultSwiftDirs(String system) throws IOException {
        List swiftDirs = new ArrayList<>();
        String xcodePath = ToolchainUtil.findXcodePath();

        // keep runtime location of swift libs. these will be checked against
        // SDK root for .tbd files
        swiftDirs.add(new File("/usr/lib/swift"));
        // add "swift" dir if it exists
        validateAndAddSwiftDir(swiftDirs, xcodePath, "swift", system);

        // find all versioned dirs and sort them descending by version
        File rootDir = new File(xcodePath, "Toolchains/XcodeDefault.xctoolchain/usr/lib/");
        final String swiftDirPrefix = "swift-";
        String[] dirNames = rootDir.list((dir, name) -> name.startsWith(swiftDirPrefix));
        if (dirNames != null && dirNames.length > 0) {
            Map swiftVersions = new HashMap<>();
            for (String dirName: dirNames) {
                Version v = Version.parseOrNull(dirName.substring(swiftDirPrefix.length()));
                if (v != null) {
                    swiftVersions.put(dirName, v);
                } else config.getLogger().warn("Failed to parse swift version: " + dirName);
            }


            // descending sort by version number
            swiftVersions.entrySet().stream()
                    .sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue()))
                    .forEach(e -> {
                        String version = e.getKey();
                        validateAndAddSwiftDir(swiftDirs, xcodePath, version, system);
                    });
        }

        return swiftDirs.toArray(new File[0]);
    }

	protected void copySwiftLibs(Collection swiftLibraries, Collection weakSwiftLibraries,
                                 File targetDir, boolean strip) throws IOException {
		File[] swiftDirs = getSwiftDirs(config);

		// dkimitsa: there is hidden dependencies possible between swift libraries.
		// e.g. one swiftLib has dependency that is not listed in included framework
		// solve this by moving through all swiftLibs and resolve their not listed dependencies
		Set libsToResolve = new HashSet<>(swiftLibraries);
        Set weakLibs = new HashSet<>(weakSwiftLibraries);
		Map resolvedLibs = new HashMap<>();
		while (!libsToResolve.isEmpty()) {
			for (String library : new HashSet<>(libsToResolve)) {
				libsToResolve.remove(library);
				if (!resolvedLibs.containsKey(library)) {
                    File swiftLibrary = locateSwiftLib(swiftDirs, library);
                    if (swiftLibrary != null) {
                        resolvedLibs.put(library, swiftLibrary);
                        getSwiftDependencies(swiftLibrary, libsToResolve, weakLibs);
                    } else {
                        if (weakLibs.contains(library))
                            config.getLogger().warn("Weak " + library + " is not found in swift paths");
                        else throw new FileNotFoundException(library + " is not found in swift paths");
                    }
                }
			}
		}

		for (Map.Entry e  : resolvedLibs.entrySet()) {
		    String library = e.getKey();
            File swiftLibrary = e.getValue();
            config.getLogger().info("Copying swift lib %s from %s to %s", library, swiftLibrary.getParent(), targetDir);
			FileUtils.copyFileToDirectory(swiftLibrary, targetDir);

			// don't strip if libraries goes to SwiftSupport folder
			if (strip) {
                // remove simulator and deprecated archs, also strip bitcode if not used
                if (config.getOs() == OS.ios && config.getArch().getEnv() != Environment.Simulator) {
                    File libFile = new File(targetDir, swiftLibrary.getName());
                    stripExtraArches(libFile);
                    if (!config.isEnableBitcode())
                        stripBitcode(libFile);
                }
            }
        }
	}

    /**
     * removes all architectures extra architectures other than binary is build for from mach-o binary (framework, lib, appext)
     */
	protected void stripExtraArches(File libFile) throws IOException {
        String archs = ToolchainUtil.lipoInfo(config, libFile);
        List archesToRemove = new ArrayList<>();

        // simulator ones
        if(archs.contains("i386")) {
            archesToRemove.add("i386");
        }
        if(archs.contains(CpuArch.x86_64.getClangName())) {
            archesToRemove.add(CpuArch.x86_64.getClangName());
        }
        // also arm64e has to be removed since Xcode10.1
        if (archs.contains("arm64e")) {
            archesToRemove.add("arm64e");
        }

        if (!archesToRemove.isEmpty()) {
            File tmpFile = new File(libFile.getAbsolutePath() + ".tmp");
            ToolchainUtil.lipoRemoveArchs(config, libFile, tmpFile, archesToRemove.toArray(new String[0]));
            FileUtils.copyFile(tmpFile, libFile);
            tmpFile.delete();
        }
    }

    /** removes bitcode from frameworks/libraries to minimize size */
    protected void stripBitcode(File libFile) throws IOException {
        File tmpFile = new File(libFile.getAbsolutePath() + ".tmp");
        ToolchainUtil.bitcodeStrip(config, libFile, tmpFile);
        FileUtils.copyFile(tmpFile, libFile);
        tmpFile.delete();
    }

    protected boolean isDynamicLibrary(File file) throws IOException {
        String result = ToolchainUtil.file(file);
        return result.contains("shared library");
    }

    protected boolean isStaticLibrary(File file) throws IOException {
        String result = ToolchainUtil.file(file);
        return result.contains("ar archive");
    }

    protected boolean isAppExtension(File file) throws IOException {
        String result = ToolchainUtil.file(file);
        return result.contains("Mach-O 64-bit executable") || result.contains("Mach-O executable");
    }

    protected void copyFile(Resource resource, File file, File destDir) throws IOException {
        config.getLogger().info("Copying resource %s to %s", file, destDir);
        FileUtils.copyFileToDirectory(file, destDir, true);
    }

    public void install() throws IOException {
        config.getLogger().info("Installing %s binary to %s", config.getTarget().getType(), config.getInstallDir());
        config.getInstallDir().mkdirs();
        doInstall(config.getInstallDir(), config.getExecutableName(), config.getInstallDir());
    }

    @Override
    public List getDefaultArchs() {
        return Collections.emptyList();
    }

    @Override
    public void archive() throws IOException {
        throw new UnsupportedOperationException("Archiving is not supported for this target");
    }

    protected void doInstall(File installDir, String image, File resourcesDir) throws IOException {
        File executable = new File(installDir, image);;
        File f = new File(config.getTmpDir(), config.getExecutableName());
        if (!f.equals(executable)) {
            FileUtils.copyFile(f, executable);
            executable.setExecutable(true, false);
        }
        doInstall(installDir, executable, resourcesDir);
    }

    protected void doInstall(File installDir, File executable, File resourcesDir) throws IOException {
        for (File f : config.getOsArchDepLibDir().listFiles()) {
            if (f.getName().matches(".*\\.(so|dylib)(\\.1)?")) {
                FileUtils.copyFileToDirectory(f, installDir);
            }
        }
        stripArchives(installDir);
        copyResources(resourcesDir);
        copyDynamicFrameworks(installDir, executable);
        copyAppExtensions(installDir);
        copyWatchApp(installDir);
    }

    public Process launch(LaunchParameters launchParameters) throws IOException {
        if (config.isSkipLinking()) {
            throw new IllegalStateException("Cannot skip linking if target should be run");
        }

        // Add -rvm:log=warn to command line arguments if no logging level has been set explicitly
        boolean add = true;
        for (String arg : launchParameters.getArguments()) {
            if (arg.startsWith("-rvm:log=")) {
                add = false;
                break;
            }
        }
        if (add) {
            List args = new ArrayList(launchParameters.getArguments());
            args.add(0, "-rvm:log=warn");
            launchParameters.setArguments(args);
        }

        Map env = new HashMap<>(launchParameters.getEnvironment() != null
                ? launchParameters.getEnvironment() : Collections.emptyMap());
        env.put("ROBOVM_LAUNCH_MODE", config.isDebug() ? "debug" : "release");
        launchParameters.setEnvironment(env);

        return doLaunch(launchParameters);
    }

    protected Process doLaunch(LaunchParameters launchParameters) throws IOException {
        return createLauncher(launchParameters).execAsync();
    }

    protected Launcher createLauncher(LaunchParameters launchParameters) throws IOException {
        throw new UnsupportedOperationException();
    }

    protected Target build(Config config) {
        return this;
    }

    protected void stripArchives(File installDir) throws IOException {
        List allPaths = new ArrayList();
        allPaths.addAll(config.getClazzes().getPaths());
        allPaths.addAll(config.getResourcesPaths());
        for (Path path : allPaths) {
            File destJar = new File(installDir, getInstallRelativeArchivePath(path));
            if (!destJar.getParentFile().exists()) {
                destJar.getParentFile().mkdirs();
            }
            stripArchive(path, destJar);
        }
    }

    protected void stripArchive(Path path, File output) throws IOException {

        if (!config.isClean() && output.exists() && !path.hasChangedSince(output.lastModified())) {
            config.getLogger().info("Not creating stripped archive file %s for unchanged path %s",
                    output, path.getFile());
            return;
        }

        config.getLogger().info("Creating stripped archive file %s", output);

        ZipOutputStream out = null;
        final ArrayList patterns = config.getStripArchivesConfig().getPatterns();
        try {
            out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(output)));

            if (path.getFile().isFile()) {
                ZipFile archive = null;
                try {
                    archive = new ZipFile(path.getFile());
                    Enumeration entries = archive.entries();
                    entryLoop : while (entries.hasMoreElements()) {
                        ZipEntry entry = entries.nextElement();
                        if (entry.getName().startsWith("META-INF/robovm/")) {
                            // Don't include anything under META-INF/robovm/
                            continue;
                        }
                        for(StripArchivesConfig.Pattern pattern : patterns){
                        	if(pattern.matches(entry.getName())){
                        		if(pattern.isInclude()){
                        			break;
                        		}
                        		continue entryLoop;
                        	}
                        }
                        ZipEntry newEntry = new ZipEntry(entry.getName());
                        newEntry.setTime(entry.getTime());
                        out.putNextEntry(newEntry);
                        InputStream in = null;
                        try {
                            in = archive.getInputStream(entry);
                            IOUtils.copy(in, out);
                            out.closeEntry();
                        } finally {
                            IOUtils.closeQuietly(in);
                        }
                    }
                } finally {
                    try {
                        archive.close();
                    } catch (Throwable t) {}
                }
            } else {
                String basePath = path.getFile().getAbsolutePath();
                @SuppressWarnings("unchecked")
                Collection files = FileUtils.listFiles(path.getFile(), null, true);
                fileLoop: for (File f : files) {
                    String entryName = f.getAbsolutePath().substring(basePath.length() + 1);
                    if (entryName.startsWith("META-INF/robovm/")) {
                        // Don't include anything under META-INF/robovm/
                        continue;
                    }
                    for(StripArchivesConfig.Pattern pattern : patterns){
                    	if(pattern.matches(entryName)){
                    		if(pattern.isInclude()){
                    			break;
                    		}
                    		continue fileLoop;
                    	}
                    }
                    ZipEntry newEntry = new ZipEntry(entryName);
                    newEntry.setTime(f.lastModified());
                    out.putNextEntry(newEntry);
                    InputStream in = null;
                    try {
                        in = new FileInputStream(f);
                        IOUtils.copy(in, out);
                        out.closeEntry();
                    } finally {
                        IOUtils.closeQuietly(in);
                    }
                }
            }
        } catch (IOException e) {
            IOUtils.closeQuietly(out);
            output.delete();
            config.getLogger().error("Filed to strip archive file %s due %s", output, e.getMessage());
        } finally {
            IOUtils.closeQuietly(out);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy