
org.gradle.ide.xcode.tasks.GenerateXcodeProjectFileTask Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2017 the original author or authors.
*
* 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.gradle.ide.xcode.tasks;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSString;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import org.gradle.api.Action;
import org.gradle.api.Incubating;
import org.gradle.api.file.FileCollection;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Internal;
import org.gradle.ide.xcode.XcodeProject;
import org.gradle.ide.xcode.internal.DefaultXcodeProject;
import org.gradle.ide.xcode.internal.XcodeBinary;
import org.gradle.ide.xcode.internal.XcodePropertyAdapter;
import org.gradle.ide.xcode.internal.XcodeTarget;
import org.gradle.ide.xcode.internal.xcodeproj.GidGenerator;
import org.gradle.ide.xcode.internal.xcodeproj.PBXBuildFile;
import org.gradle.ide.xcode.internal.xcodeproj.PBXFileReference;
import org.gradle.ide.xcode.internal.xcodeproj.PBXGroup;
import org.gradle.ide.xcode.internal.xcodeproj.PBXLegacyTarget;
import org.gradle.ide.xcode.internal.xcodeproj.PBXNativeTarget;
import org.gradle.ide.xcode.internal.xcodeproj.PBXProject;
import org.gradle.ide.xcode.internal.xcodeproj.PBXReference;
import org.gradle.ide.xcode.internal.xcodeproj.PBXShellScriptBuildPhase;
import org.gradle.ide.xcode.internal.xcodeproj.PBXSourcesBuildPhase;
import org.gradle.ide.xcode.internal.xcodeproj.PBXTarget;
import org.gradle.ide.xcode.internal.xcodeproj.XcodeprojSerializer;
import org.gradle.ide.xcode.tasks.internal.XcodeProjectFile;
import org.gradle.language.swift.SwiftVersion;
import org.gradle.nativeplatform.MachineArchitecture;
import org.gradle.plugins.ide.api.PropertyListGeneratorTask;
import javax.inject.Inject;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.gradle.ide.xcode.internal.DefaultXcodeProject.TEST_DEBUG;
import static org.gradle.ide.xcode.internal.XcodeUtils.toSpaceSeparatedList;
/**
* Task for generating a project file.
*
* @since 4.2
*/
@Incubating
public class GenerateXcodeProjectFileTask extends PropertyListGeneratorTask {
private static final String PRODUCTS_GROUP_NAME = "Products";
private static final String UNBUILDABLE_BUILD_CONFIGURATION_NAME = "unbuildable";
private final GidGenerator gidGenerator;
private DefaultXcodeProject xcodeProject;
private Map pathToFileReference = new HashMap();
@Inject
public GenerateXcodeProjectFileTask(GidGenerator gidGenerator) {
this.gidGenerator = gidGenerator;
}
@Override
protected void configure(XcodeProjectFile projectFile) {
PBXProject project = new PBXProject(getProject().getPath());
addToGroup(project.getMainGroup(), xcodeProject.getGroups().getSources(), "Sources");
addToGroup(project.getMainGroup(), xcodeProject.getGroups().getHeaders(), "Headers");
addToGroup(project.getMainGroup(), xcodeProject.getGroups().getTests(), "Tests");
addToGroup(project.getMainGroup(), xcodeProject.getGroups().getRoot());
for (XcodeTarget xcodeTarget : xcodeProject.getTargets()) {
if (xcodeTarget.isBuildable()) {
project.getTargets().add(toGradlePbxTarget(xcodeTarget));
} else {
getLogger().warn("'" + xcodeTarget.getName() + "' component in project '" + getProject().getPath() + "' is not buildable.");
}
project.getTargets().add(toIndexPbxTarget(xcodeTarget));
if (!xcodeTarget.isUnitTest() && xcodeTarget.getDebugOutputFile().isPresent()) {
File debugOutputFile = xcodeTarget.getDebugOutputFile().get().getAsFile();
PBXFileReference fileReference = new PBXFileReference(debugOutputFile.getName(), debugOutputFile.getAbsolutePath(), PBXReference.SourceTree.ABSOLUTE);
fileReference.setExplicitFileType(Optional.of(xcodeTarget.getOutputFileType()));
project.getMainGroup().getOrCreateChildGroupByName(PRODUCTS_GROUP_NAME).getChildren().add(fileReference);
}
}
// Create build configuration at the project level from all target's build configuration
project.getTargets().stream().flatMap(it -> it.getBuildConfigurationList().getBuildConfigurationsByName().asMap().keySet().stream()).forEach(project.getBuildConfigurationList().getBuildConfigurationsByName()::getUnchecked);
XcodeprojSerializer serializer = new XcodeprojSerializer(gidGenerator, project);
final NSDictionary rootObject = serializer.toPlist();
projectFile.transformAction(new Action() {
@Override
public void execute(NSDictionary dict) {
dict.clear();
dict.putAll(rootObject);
}
});
}
private void addToGroup(PBXGroup mainGroup, FileCollection sources, String groupName) {
if (!sources.isEmpty()) {
addToGroup(mainGroup.getOrCreateChildGroupByName(groupName), sources);
}
}
private void addToGroup(PBXGroup group, Iterable sources) {
for (File source : sources) {
PBXFileReference fileReference = toFileReference(source);
pathToFileReference.put(source.getAbsolutePath(), fileReference);
group.getChildren().add(fileReference);
}
}
private List getAllBinaries() {
return xcodeProject.getTargets().stream().map(XcodeTarget::getBinaries).flatMap(Collection::stream).collect(Collectors.toList());
}
@Override
protected XcodeProjectFile create() {
return new XcodeProjectFile(getPropertyListTransformer());
}
private PBXFileReference toFileReference(File file) {
return new PBXFileReference(file.getName(), file.getAbsolutePath(), PBXReference.SourceTree.ABSOLUTE);
}
private PBXTarget toGradlePbxTarget(XcodeTarget xcodeTarget) {
if (xcodeTarget.isUnitTest()) {
return toXCTestPbxTarget(xcodeTarget);
}
return toToolAndLibraryPbxTarget(xcodeTarget);
}
private PBXTarget toToolAndLibraryPbxTarget(XcodeTarget xcodeTarget) {
PBXLegacyTarget target = new PBXLegacyTarget(xcodeTarget.getName(), xcodeTarget.getProductType());
target.setProductName(xcodeTarget.getProductName());
target.setBuildToolPath(xcodeTarget.getGradleCommand());
target.setBuildArgumentsString(buildGradleArgs(xcodeTarget));
target.setGlobalID(xcodeTarget.getId());
File outputFile = xcodeTarget.getDebugOutputFile().get().getAsFile();
target.setProductReference(new PBXFileReference(outputFile.getName(), outputFile.getAbsolutePath(), PBXReference.SourceTree.ABSOLUTE));
xcodeTarget.getBinaries().forEach(xcodeBinary -> {
NSDictionary settings = target.getBuildConfigurationList().getBuildConfigurationsByName().getUnchecked(xcodeBinary.getBuildConfigurationName()).getBuildSettings();
File binaryOutputFile = xcodeBinary.getOutputFile().get().getAsFile();
settings.put("CONFIGURATION_BUILD_DIR", new NSString(binaryOutputFile.getParentFile().getAbsolutePath()));
settings.put("PRODUCT_NAME", target.getProductName());
settings.put("SWIFT_VERSION", toXcodeSwiftVersion(xcodeTarget.getSwiftSourceCompatibility()));
settings.put("ARCHS", toXcodeArchitecture(xcodeBinary.getArchitectureName()));
settings.put("VALID_ARCHS", xcodeTarget.getBinaries().stream().map(XcodeBinary::getArchitectureName).map(GenerateXcodeProjectFileTask::toXcodeArchitecture).distinct().collect(Collectors.joining(" ")));
});
return target;
}
private String buildGradleArgs(XcodeTarget xcodeTarget) {
return Joiner.on(' ').join(XcodePropertyAdapter.getAdapterCommandLine()) + " " + xcodeTarget.getTaskName();
}
private PBXTarget toXCTestPbxTarget(XcodeTarget xcodeTarget) {
PBXShellScriptBuildPhase hackBuildPhase = new PBXShellScriptBuildPhase();
hackBuildPhase.setShellPath("/bin/sh");
hackBuildPhase.setShellScript("# Script to generate specific Swift files Xcode expects when running tests.\n"
+ "set -eu\n"
+ "ARCH_ARRAY=($ARCHS)\n"
+ "SUFFIXES=(swiftdoc swiftmodule h)\n"
+ "for ARCH in \"${ARCH_ARRAY[@]}\"\n"
+ "do\n"
+ " for SUFFIX in \"${SUFFIXES[@]}\"\n"
+ " do\n"
+ " touch \"$OBJECT_FILE_DIR_normal/$ARCH/$PRODUCT_NAME.$SUFFIX\"\n"
+ " done\n"
+ "done");
PBXShellScriptBuildPhase gradleBuildPhase = new PBXShellScriptBuildPhase();
gradleBuildPhase.setShellPath("/bin/sh");
gradleBuildPhase.setShellScript("exec \"" + xcodeTarget.getGradleCommand() + "\" " + buildGradleArgs(xcodeTarget) + " < /dev/null");
PBXNativeTarget target = new PBXNativeTarget(xcodeTarget.getName(), xcodeTarget.getProductType());
target.setProductName(xcodeTarget.getProductName());
target.setGlobalID(xcodeTarget.getId());
// Note the order in which the build phase are added is important
target.getBuildPhases().add(hackBuildPhase);
target.getBuildPhases().add(newSourceBuildPhase(xcodeTarget.getSources()));
target.getBuildPhases().add(gradleBuildPhase);
File outputFile = xcodeTarget.getDebugOutputFile().get().getAsFile();
target.setProductReference(new PBXFileReference(outputFile.getName(), outputFile.getAbsolutePath(), PBXReference.SourceTree.ABSOLUTE));
getAllBinaries().stream().filter(it -> !Objects.equals(it.getBuildConfigurationName(), TEST_DEBUG)).forEach(configureBuildSettings(xcodeTarget, target));
NSDictionary testRunnerSettings = target.getBuildConfigurationList().getBuildConfigurationsByName().getUnchecked(TEST_DEBUG).getBuildSettings();
if (!xcodeTarget.getCompileModules().isEmpty()) {
testRunnerSettings.put("SWIFT_INCLUDE_PATHS", toSpaceSeparatedList(parentDirs(xcodeTarget.getCompileModules())));
}
testRunnerSettings.put("SWIFT_VERSION", toXcodeSwiftVersion(xcodeTarget.getSwiftSourceCompatibility()));
testRunnerSettings.put("PRODUCT_NAME", target.getProductName());
testRunnerSettings.put("OTHER_LDFLAGS", "-help");
testRunnerSettings.put("OTHER_CFLAGS", "-help");
testRunnerSettings.put("OTHER_SWIFT_FLAGS", "-help");
testRunnerSettings.put("SWIFT_INSTALL_OBJC_HEADER", "NO");
testRunnerSettings.put("SWIFT_OBJC_INTERFACE_HEADER_NAME", "$(PRODUCT_NAME).h");
return target;
}
private PBXTarget toIndexPbxTarget(XcodeTarget xcodeTarget) {
PBXNativeTarget target = new PBXNativeTarget("[INDEXING ONLY] " + xcodeTarget.getName(), PBXTarget.ProductType.INDEXER);
target.setProductName(xcodeTarget.getProductName());
target.getBuildPhases().add(newSourceBuildPhase(xcodeTarget.getSources()));
xcodeTarget.getBinaries().forEach(configureBuildSettings(xcodeTarget, target));
// Create unbuildable build configuration so the indexer can keep functioning
if (xcodeTarget.getBinaries().isEmpty()) {
NSDictionary settings = newBuildSettings(xcodeTarget);
target.getBuildConfigurationList().getBuildConfigurationsByName().getUnchecked(UNBUILDABLE_BUILD_CONFIGURATION_NAME).setBuildSettings(settings);
}
return target;
}
private Consumer configureBuildSettings(XcodeTarget xcodeTarget, PBXNativeTarget target) {
return xcodeBinary -> {
NSDictionary settings = newBuildSettings(xcodeTarget);
settings.put("ARCHS", toXcodeArchitecture(xcodeBinary.getArchitectureName()));
settings.put("VALID_ARCHS", xcodeTarget.getBinaries().stream().map(it -> GenerateXcodeProjectFileTask.toXcodeArchitecture(it.getArchitectureName())).distinct().collect(Collectors.joining(" ")));
target.getBuildConfigurationList().getBuildConfigurationsByName().getUnchecked(xcodeBinary.getBuildConfigurationName()).setBuildSettings(settings);
};
}
private PBXSourcesBuildPhase newSourceBuildPhase(FileCollection sourceFiles) {
PBXSourcesBuildPhase result = new PBXSourcesBuildPhase();
for (File file : sourceFiles) {
PBXFileReference fileReference = pathToFileReference.get(file.getAbsolutePath());
result.getFiles().add(new PBXBuildFile(fileReference));
}
return result;
}
private NSDictionary newBuildSettings(XcodeTarget xcodeTarget) {
NSDictionary result = new NSDictionary();
result.put("SWIFT_VERSION", toXcodeSwiftVersion(xcodeTarget.getSwiftSourceCompatibility()));
result.put("PRODUCT_NAME", xcodeTarget.getProductName()); // Mandatory
if (!xcodeTarget.getHeaderSearchPaths().isEmpty()) {
result.put("HEADER_SEARCH_PATHS", toSpaceSeparatedList(xcodeTarget.getHeaderSearchPaths()));
}
if (!xcodeTarget.getCompileModules().isEmpty()) {
result.put("SWIFT_INCLUDE_PATHS", toSpaceSeparatedList(parentDirs(xcodeTarget.getCompileModules())));
}
return result;
}
private static String toXcodeArchitecture(String architectureName) {
if (architectureName.equals(MachineArchitecture.X86)) {
return "i386";
} else if (architectureName.equals(MachineArchitecture.X86_64)) {
return "x86_64";
}
return architectureName;
}
private static String toXcodeSwiftVersion(Provider swiftVersion) {
if (swiftVersion.isPresent()) {
return String.format("%d.0", swiftVersion.get().getVersion());
}
return null;
}
private static Iterable parentDirs(Iterable files) {
List parents = new ArrayList();
for (File file : files) {
if (file.isDirectory()) {
parents.add(file);
} else {
parents.add(file.getParentFile());
}
}
return parents;
}
@Internal
public XcodeProject getXcodeProject() {
return xcodeProject;
}
public void setXcodeProject(XcodeProject xcodeProject) {
this.xcodeProject = (DefaultXcodeProject) xcodeProject;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy