Skip to content

Commit

Permalink
* maintenance: interface builder integration
Browse files Browse the repository at this point in the history
Main reason -- to be able to compile Stub in Xcode without errors.

What was fixed:
## Cannot code sign because the target does not have an Info.plist file
Info.plist is now generated and included into xcode project.

## Undefined symbols for architecture arm64: "_main"
Stub `int main() {}` is now generated and included into xcode project.

## multiple `FrameworkName/FrameworkName.h` not found
Error while compiling pre-compiled header file where all framework are being referenced.
XCode doesn't seem to recognise single arch framework.
Changes where done:
- propagate XCFrameworks instead of its framework.
- clang modules are enabled now;
- not all frameworks have umbrella header with same name, e.g. `FrameworkName/FrameworkName.h`, logic added to look for Swift umbrellas as well.
- if umbrella header is not found -- framework is not included into pre-compiled headers.

To have manual control over the logic and to be able to add extra imports and filter out not required config was extended with following section:
```xml
<config>
    <tools>
        <ibx>
            <pch>
                <include>MyFramework/MyFramework.h</include>
                <include import="true">MyModule</include>
                <filter exclude="true">*Promises*</filter>
            </pch>
        </ibx>
    </tools>
</config>
```

`pch` section allow to include additional frameworks with `include` tags. if `import` attribute is specified -- `@import` will be used instead of `#import` (works for modules).
`filter` tag allows to exclude from pre-compiled header files reference to not required framework. For example `FBLPromises` causes following error:
> fatal error: module 'PromisesObjC' in AST file
  • Loading branch information
dkimitsa committed May 19, 2024
1 parent 833f6d3 commit 837f52e
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ public boolean shouldEmitBitcode() {
}

public Tools getTools() {
return tools;
return tools != null ? tools : Tools.Empty;
}

public WatchKitApp getWatchKitApp() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.robovm.compiler.config.tools;

import org.simpleframework.xml.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Settings to control Interface Builder/Xcode integration, appears in tools section of config:
* <config>
* <tools>
* <ibx>
* <include>MyFramework/MyFramework.h</>
* <include import="true">MyModule</>
* <filter exclude=true>FBLPromises</filter>
* <ibx/>
* </tools>
* </config>
* @author dkimitsa
*/
public class IBXOptions {
/**
* pre-compiled headers file generation options
*/
@Element(required = false)
private PCHOptions pch;

public PCHOptions getPrecompileHeadersOptions() {
return pch;
}

public static class PCHOptions {
@ElementList(required = false, entry = "include", inline = true)
private ArrayList<IncludeEntry> includes;

@ElementList(required = false, entry = "filter", inline = true)
private ArrayList<FilterEntry> filters;

public List<IncludeEntry> getIncludes() {
return includes != null ? includes : Collections.emptyList();
}

public List<FilterEntry> getFilters() {
return filters != null ? filters : Collections.emptyList();
}
}

public static class IncludeEntry {
@Text
String includeText;

@Attribute(name = "import", required = false)
boolean isImport = false;

/**
* @return text to be included into `#include <includeText>` or `@import includeText;`
*/
public String getIncludeText() {
return includeText;
}

public boolean isImport() {
return isImport;
}
}

public static class FilterEntry {
@Text
String antPathPattern;

@Attribute(name = "exclude", required = false)
boolean exclude = false;

public String getAntPathPattern() {
return antPathPattern;
}

public boolean isExclude() {
return exclude;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
*
*/
public class Tools {
public static Tools Empty = new Tools();

@Element(required = false)
private TextureAtlas textureAtlas;

Expand All @@ -32,6 +34,9 @@ public class Tools {
@Element(required = false)
private ActoolOptions actool;

@Element(required = false)
private IBXOptions ibx;

public TextureAtlas getTextureAtlas() {
return textureAtlas;
}
Expand All @@ -43,4 +48,8 @@ public LinkerOptions getLinker() {
public ActoolOptions getActool() {
return actool;
}

public IBXOptions getIbx() {
return ibx;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ protected Process doLaunch(LaunchParameters launchParameters) throws IOException

@Override
public List<Arch> getDefaultArchs() {
return Arrays.asList(new Arch(CpuArch.thumbv7), new Arch(CpuArch.arm64));
return List.of(new Arch(CpuArch.arm64));
}

public void archive() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.robovm.compiler.config.Config;
import org.robovm.compiler.config.Resource;
import org.robovm.compiler.log.Logger;
import org.robovm.compiler.util.InfoPList;
import org.robovm.ibxcode.export.FrameworkExportData;
import org.robovm.ibxcode.export.IBClassExportData;
import org.robovm.ibxcode.export.XCodeProjectExporter;
Expand All @@ -29,7 +30,6 @@
import org.robovm.ibxcode.parser.IBClassMemberParser;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitResult;
Expand Down Expand Up @@ -94,8 +94,14 @@ public void generate(File projectDir, File exportDir, String projectName, boolea
// resolve frameworks list
List<FrameworkExportData> frameworks = resolveFrameworks();

// resolve info.plist entries
InfoPList plist = config.getInfoPList();
plist.parse(config.getProperties());

// now create and write xcode project
XCodeProjectExporter projectExporter = new XCodeProjectExporter(exportDatas, resources, frameworks,
XCodeProjectExporter projectExporter = new XCodeProjectExporter(
config.getTools().getIbx(),
exportDatas, resources, frameworks, plist,
projectDir, exportDir, projectName);
projectExporter.export();

Expand All @@ -106,15 +112,15 @@ public void generate(File projectDir, File exportDir, String projectName, boolea

private void processDirectoryClassPath(final Path dirPath, final Map<String, JavaClass> classesData) {
// add files from directory
SimpleFileVisitor<Path> walker = new SimpleFileVisitor<Path>() {
SimpleFileVisitor<Path> walker = new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (!file.getFileName().toString().endsWith(".class"))
return CONTINUE;
String relative = dirPath.relativize(file).toString();
if (Utils.isSystemLikeClassPath(relative.substring(0, relative.length() - ".class".length())))
return CONTINUE;
try (InputStream is = new FileInputStream(file.toFile())) {
try (InputStream is = Files.newInputStream(file.toFile().toPath())) {
// get class path for early skip
ClassParser cp = new ClassParser(is, file.toString());
JavaClass jc = cp.parse();
Expand Down Expand Up @@ -180,11 +186,13 @@ private void prepareExportDir(File exportDir, String projectName) {
for (File file : files) {
if (file.getName().equals(projectName + ".xcodeproj"))
continue;
if (file.isFile())
if (!file.delete())
if (file.isFile()) {
if (!file.delete()) {
throw new IOException("Can't delete " + file.getAbsolutePath());
else
}
} else {
FileUtils.deleteDirectory(file);
}
}
}
} catch (IOException e) {
Expand All @@ -203,29 +211,44 @@ private List<FrameworkExportData> resolveFrameworks() {
List<String> frameworkList = config.getFrameworks();
if (!frameworkList.contains("UIKIT")) {
// add uikit to be present in precompiled headers
frameworkList = new ArrayList<>();
frameworkList.addAll(config.getFrameworks());
frameworkList = new ArrayList<>(config.getFrameworks());
frameworkList.add("UIKIT");
}
List<File> frameworkPaths = config.getFrameworkPaths();
for (String name : frameworkList) {
// add suffix if it is not there
String frameWorkName = name.endsWith(".framework") ? name : name + ".framework";
String xcFrameWorkName = name.endsWith(".xcframework") ? name : name + ".xcframework";
// look for framework in all possible framework locations
FrameworkExportData frameWorkData = null;
if (frameworkPaths != null) {
File xcFrameWorkPath = null;
for (File path : frameworkPaths) {
if (xcFrameWorkPath == null) {
// looks for possible xcframework and save it
// xcframework location expected to be before resolved location of particular
// framework for target arch in it. that is how resolver in Config class
// works.
File candidate = new File(path, xcFrameWorkName);
if (candidate.isDirectory())
xcFrameWorkPath = candidate;
}

File frameWorkPath = new File(path, frameWorkName);
if (!frameWorkPath.isDirectory())
continue;
// it is local framework
frameWorkData = new FrameworkExportData(frameWorkName, frameWorkPath);
break;
if (frameWorkPath.isDirectory()) {
// framework found, but if there was a xcframework located before, save data
// as xcframework one
if (xcFrameWorkPath != null)
frameWorkData = new FrameworkExportData(xcFrameWorkName, xcFrameWorkPath, frameWorkPath);
else
frameWorkData = new FrameworkExportData(frameWorkName, frameWorkPath, frameWorkPath);
break;
}
}
}
if (frameWorkData == null) {
// probably global framework
frameWorkData = new FrameworkExportData(frameWorkName, null);
frameWorkData = new FrameworkExportData(frameWorkName, null, null);
}

frameworks.add(frameWorkData);
Expand All @@ -238,7 +261,7 @@ private List<File> resolveResources() {
// add all to map. key is target location in destination folder.
// so this wil emulate override resource (.e.g later declared resource will override previous one)
final Map<String, File> resources = new HashMap<>();
if (config.getResources() == null || config.getResources().size() == 0)
if (config.getResources() == null || config.getResources().isEmpty())
return Collections.emptyList();

// contains list of groups. If any file inside this group it has to be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@
public class FrameworkExportData {
public final String name;
public final File path;
public final File frameworkPath;

public FrameworkExportData(String name, File path) {
/**
* @param name - name of framework with .framework or .xcframework suffix
* @param path - location of framework: either .xcframework or .framwork folder
* @param frameworkPath - location of framework in case of .xcframwork otherwise same as path
*/
public FrameworkExportData(String name, File path, File frameworkPath) {
this.name = name;
this.path = path;
this.frameworkPath = frameworkPath;
}
}
Loading

0 comments on commit 837f52e

Please sign in to comment.