
org.gradle.ide.xcode.plugins.XcodePlugin 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.plugins;
import org.apache.commons.lang.StringUtils;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Incubating;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.artifacts.ArtifactView;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.component.ComponentIdentifier;
import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
import org.gradle.api.component.SoftwareComponent;
import org.gradle.api.file.FileCollection;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.specs.Spec;
import org.gradle.api.tasks.Delete;
import org.gradle.api.tasks.Sync;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.ide.xcode.XcodeExtension;
import org.gradle.ide.xcode.XcodeRootExtension;
import org.gradle.ide.xcode.internal.DefaultXcodeExtension;
import org.gradle.ide.xcode.internal.DefaultXcodeProject;
import org.gradle.ide.xcode.internal.DefaultXcodeRootExtension;
import org.gradle.ide.xcode.internal.DefaultXcodeWorkspace;
import org.gradle.ide.xcode.internal.XcodeProjectMetadata;
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.PBXTarget;
import org.gradle.ide.xcode.tasks.GenerateSchemeFileTask;
import org.gradle.ide.xcode.tasks.GenerateWorkspaceSettingsFileTask;
import org.gradle.ide.xcode.tasks.GenerateXcodeProjectFileTask;
import org.gradle.ide.xcode.tasks.GenerateXcodeWorkspaceFileTask;
import org.gradle.internal.Actions;
import org.gradle.language.cpp.CppBinary;
import org.gradle.language.cpp.CppExecutable;
import org.gradle.language.cpp.CppLibrary;
import org.gradle.language.cpp.CppSharedLibrary;
import org.gradle.language.cpp.CppStaticLibrary;
import org.gradle.language.cpp.ProductionCppComponent;
import org.gradle.language.cpp.internal.DefaultCppBinary;
import org.gradle.language.cpp.plugins.CppApplicationPlugin;
import org.gradle.language.cpp.plugins.CppLibraryPlugin;
import org.gradle.language.swift.ProductionSwiftComponent;
import org.gradle.language.swift.SwiftBinary;
import org.gradle.language.swift.SwiftExecutable;
import org.gradle.language.swift.SwiftSharedLibrary;
import org.gradle.language.swift.SwiftStaticLibrary;
import org.gradle.language.swift.internal.DefaultSwiftBinary;
import org.gradle.language.swift.plugins.SwiftApplicationPlugin;
import org.gradle.language.swift.plugins.SwiftLibraryPlugin;
import org.gradle.nativeplatform.test.xctest.SwiftXCTestSuite;
import org.gradle.nativeplatform.test.xctest.plugins.XCTestConventionPlugin;
import org.gradle.plugins.ide.internal.IdeArtifactRegistry;
import org.gradle.plugins.ide.internal.IdePlugin;
import org.gradle.util.CollectionUtils;
import javax.inject.Inject;
import java.io.File;
/**
* A plugin for creating a XCode project for a gradle project.
*
* @since 4.2
*/
@Incubating
public class XcodePlugin extends IdePlugin {
private final GidGenerator gidGenerator;
private final ObjectFactory objectFactory;
private final IdeArtifactRegistry artifactRegistry;
private DefaultXcodeProject xcodeProject;
@Inject
public XcodePlugin(GidGenerator gidGenerator, ObjectFactory objectFactory, IdeArtifactRegistry artifactRegistry) {
this.gidGenerator = gidGenerator;
this.objectFactory = objectFactory;
this.artifactRegistry = artifactRegistry;
}
@Override
protected String getLifecycleTaskName() {
return "xcode";
}
@Override
protected void onApply(final Project project) {
TaskProvider extends Task> lifecycleTask = getLifecycleTask();
lifecycleTask.configure(withDescription("Generates XCode project files (pbxproj, xcworkspace, xcscheme)"));
if (isRoot()) {
DefaultXcodeRootExtension xcode = (DefaultXcodeRootExtension) project.getExtensions().create(XcodeRootExtension.class, "xcode", DefaultXcodeRootExtension.class, objectFactory);
xcodeProject = xcode.getProject();
final GenerateXcodeWorkspaceFileTask workspaceTask = createWorkspaceTask(project, xcode.getWorkspace());
lifecycleTask.configure(dependsOn(workspaceTask));
addWorkspace(xcode.getWorkspace());
} else {
DefaultXcodeExtension xcode = (DefaultXcodeExtension) project.getExtensions().create(XcodeExtension.class, "xcode", DefaultXcodeExtension.class, objectFactory);
xcodeProject = xcode.getProject();
}
xcodeProject.setLocationDir(project.file(project.getName() + ".xcodeproj"));
GenerateXcodeProjectFileTask projectTask = createProjectTask((ProjectInternal) project);
lifecycleTask.configure(dependsOn(projectTask));
project.getTasks().addRule("Xcode bridge tasks begin with _xcode. Do not call these directly.", new XcodeBridge(xcodeProject, project));
configureForSwiftPlugin(project);
configureForCppPlugin(project);
includeBuildFilesInProject(project);
configureXcodeCleanTask(project);
}
private void includeBuildFilesInProject(Project project) {
// TODO: Add other build like files `build.gradle.kts`, `settings.gradle(.kts)`, other `.gradle`, `gradle.properties`
if (project.getBuildFile().exists()) {
xcodeProject.getGroups().getRoot().from(project.getBuildFile());
}
}
private void configureXcodeCleanTask(Project project) {
Delete cleanTask = project.getTasks().create("cleanXcodeProject", Delete.class);
cleanTask.delete(xcodeProject.getLocationDir());
if (isRoot()) {
cleanTask.delete(project.file(project.getName() + ".xcworkspace"));
}
getCleanTask().configure(Actions.composite(withDescription("Cleans XCode project files (xcodeproj)"), dependsOn(cleanTask)));
}
private GenerateXcodeProjectFileTask createProjectTask(final ProjectInternal project) {
File xcodeProjectPackageDir = xcodeProject.getLocationDir();
GenerateWorkspaceSettingsFileTask workspaceSettingsFileTask = project.getTasks().create("xcodeProjectWorkspaceSettings", GenerateWorkspaceSettingsFileTask.class);
workspaceSettingsFileTask.setOutputFile(new File(xcodeProjectPackageDir, "project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings"));
GenerateXcodeProjectFileTask projectFileTask = project.getTasks().create("xcodeProject", GenerateXcodeProjectFileTask.class);
projectFileTask.dependsOn(workspaceSettingsFileTask);
projectFileTask.dependsOn(xcodeProject.getTaskDependencies());
projectFileTask.dependsOn(project.getTasks().withType(GenerateSchemeFileTask.class));
projectFileTask.setXcodeProject(xcodeProject);
projectFileTask.setOutputFile(new File(xcodeProjectPackageDir, "project.pbxproj"));
artifactRegistry.registerIdeProject(new XcodeProjectMetadata(xcodeProject, projectFileTask));
return projectFileTask;
}
private GenerateXcodeWorkspaceFileTask createWorkspaceTask(Project project, DefaultXcodeWorkspace workspace) {
File xcodeWorkspacePackageDir = project.file(project.getName() + ".xcworkspace");
workspace.getLocation().set(xcodeWorkspacePackageDir);
GenerateWorkspaceSettingsFileTask workspaceSettingsFileTask = project.getTasks().create("xcodeWorkspaceWorkspaceSettings", GenerateWorkspaceSettingsFileTask.class);
workspaceSettingsFileTask.setOutputFile(new File(xcodeWorkspacePackageDir, "xcshareddata/WorkspaceSettings.xcsettings"));
GenerateXcodeWorkspaceFileTask workspaceFileTask = project.getTasks().create("xcodeWorkspace", GenerateXcodeWorkspaceFileTask.class);
workspaceFileTask.dependsOn(workspaceSettingsFileTask);
workspaceFileTask.setOutputFile(new File(xcodeWorkspacePackageDir, "contents.xcworkspacedata"));
workspaceFileTask.setXcodeProjectLocations(artifactRegistry.getIdeProjectFiles(XcodeProjectMetadata.class));
return workspaceFileTask;
}
private String getBridgeTaskPath(Project project) {
String projectPath = "";
if (!isRoot()) {
projectPath = project.getPath();
}
return projectPath + ":_xcode__${ACTION}_${PRODUCT_NAME}_${CONFIGURATION}";
}
private void configureForSwiftPlugin(final Project project) {
project.getPlugins().withType(SwiftApplicationPlugin.class, new Action() {
@Override
public void execute(SwiftApplicationPlugin plugin) {
configureXcodeForSwift(project);
}
});
project.getPlugins().withType(SwiftLibraryPlugin.class, new Action() {
@Override
public void execute(SwiftLibraryPlugin plugin) {
configureXcodeForSwift(project);
}
});
project.getPlugins().withType(XCTestConventionPlugin.class, new Action() {
@Override
public void execute(XCTestConventionPlugin plugin) {
configureXcodeForXCTest(project);
}
});
}
private void configureXcodeForXCTest(final Project project) {
project.afterEvaluate(new Action() {
@Override
public void execute(Project project) {
SwiftXCTestSuite component = project.getExtensions().getByType(SwiftXCTestSuite.class);
FileCollection sources = component.getSwiftSource();
xcodeProject.getGroups().getTests().from(sources);
String targetName = component.getModule().get();
final XcodeTarget target = newTarget(targetName, component.getModule().get(), toGradleCommand(project), getBridgeTaskPath(project), sources);
target.getSwiftSourceCompatibility().convention(component.getSourceCompatibility());
if (component.getTestBinary().isPresent()) {
target.addBinary(DefaultXcodeProject.BUILD_DEBUG, component.getTestBinary().get().getInstallDirectory(), component.getTestBinary().get().getTargetMachine().getArchitecture().getName());
target.addBinary(DefaultXcodeProject.BUILD_RELEASE, component.getTestBinary().get().getInstallDirectory(), component.getTestBinary().get().getTargetMachine().getArchitecture().getName());
target.setProductType(PBXTarget.ProductType.UNIT_TEST);
target.getCompileModules().from(component.getTestBinary().get().getCompileModules());
target.addTaskDependency(filterArtifactsFromImplicitBuilds(((DefaultSwiftBinary) component.getTestBinary().get()).getImportPathConfiguration()).getBuildDependencies());
}
component.getBinaries().whenElementFinalized(new Action() {
@Override
public void execute(SwiftBinary swiftBinary) {
target.getSwiftSourceCompatibility().set(swiftBinary.getTargetPlatform().getSourceCompatibility());
}
});
xcodeProject.addTarget(target);
}
});
}
private FileCollection filterArtifactsFromImplicitBuilds(Configuration configuration) {
return configuration.getIncoming().artifactView(fromSourceDependency()).getArtifacts().getArtifactFiles();
}
private void configureXcodeForSwift(final Project project) {
project.afterEvaluate(new Action() {
@Override
public void execute(final Project project) {
// TODO: Assumes there's a single 'main' Swift component
final ProductionSwiftComponent component = project.getComponents().withType(ProductionSwiftComponent.class).getByName("main");
FileCollection sources = component.getSwiftSource();
xcodeProject.getGroups().getSources().from(sources);
// TODO - should use the _install_ task for an executable
final String targetName = component.getModule().get();
final XcodeTarget target = newTarget(targetName, component.getModule().get(), toGradleCommand(project), getBridgeTaskPath(project), sources);
target.getDefaultConfigurationName().set(component.getDevelopmentBinary().map(devBinary -> toBuildConfigurationName(component, devBinary)));
component.getBinaries().whenElementFinalized(new Action() {
@Override
public void execute(SwiftBinary swiftBinary) {
if (swiftBinary instanceof SwiftExecutable) {
target.addBinary(toBuildConfigurationName(component, swiftBinary), ((SwiftExecutable) swiftBinary).getDebuggerExecutableFile(), swiftBinary.getTargetMachine().getArchitecture().getName());
target.setProductType(PBXTarget.ProductType.TOOL);
} else if (swiftBinary instanceof SwiftSharedLibrary) {
target.addBinary(toBuildConfigurationName(component, swiftBinary), ((SwiftSharedLibrary) swiftBinary).getRuntimeFile(), swiftBinary.getTargetMachine().getArchitecture().getName());
target.setProductType(PBXTarget.ProductType.DYNAMIC_LIBRARY);
} else if (swiftBinary instanceof SwiftStaticLibrary) {
target.addBinary(toBuildConfigurationName(component, swiftBinary), ((SwiftStaticLibrary) swiftBinary).getLinkFile(), swiftBinary.getTargetMachine().getArchitecture().getName());
target.setProductType(PBXTarget.ProductType.STATIC_LIBRARY);
}
target.getSwiftSourceCompatibility().set(swiftBinary.getTargetPlatform().getSourceCompatibility());
if (swiftBinary == component.getDevelopmentBinary().get()) {
target.getCompileModules().from(component.getDevelopmentBinary().get().getCompileModules());
target.addTaskDependency(filterArtifactsFromImplicitBuilds(((DefaultSwiftBinary) component.getDevelopmentBinary().get()).getImportPathConfiguration()).getBuildDependencies());
createSchemeTask(project.getTasks(), targetName, xcodeProject);
}
}
});
xcodeProject.addTarget(target);
}
});
}
private String toBuildConfigurationName(SoftwareComponent component, SoftwareComponent binary) {
String result = binary.getName().replace(component.getName(), "");
if (binary instanceof SwiftSharedLibrary || binary instanceof CppSharedLibrary) {
return result.replace("Shared", "");
} else if (binary instanceof SwiftStaticLibrary || binary instanceof CppStaticLibrary) {
return result.replace("Static", "");
}
return result;
}
private void configureForCppPlugin(final Project project) {
project.getPlugins().withType(CppApplicationPlugin.class, new Action() {
@Override
public void execute(CppApplicationPlugin plugin) {
configureXcodeForCpp(project);
}
});
project.getPlugins().withType(CppLibraryPlugin.class, new Action() {
@Override
public void execute(CppLibraryPlugin plugin) {
configureXcodeForCpp(project);
}
});
}
private void configureXcodeForCpp(Project project) {
project.afterEvaluate(new Action() {
@Override
public void execute(final Project project) {
// TODO: Assumes there's a single 'main' C++ component
final ProductionCppComponent component = project.getComponents().withType(ProductionCppComponent.class).getByName("main");
FileCollection sources = component.getCppSource();
xcodeProject.getGroups().getSources().from(sources);
FileCollection headers = component.getHeaderFiles();
xcodeProject.getGroups().getHeaders().from(headers);
// TODO - should use the _install_ task for an executable
final String targetName = StringUtils.capitalize(component.getBaseName().get());
final XcodeTarget target = newTarget(targetName, targetName, toGradleCommand(project), getBridgeTaskPath(project), sources);
target.getDefaultConfigurationName().set(component.getDevelopmentBinary().map(devBinary -> toBuildConfigurationName(component, devBinary)));
component.getBinaries().whenElementFinalized(new Action() {
@Override
public void execute(CppBinary cppBinary) {
if (cppBinary instanceof CppExecutable) {
target.addBinary(toBuildConfigurationName(component, cppBinary), ((CppExecutable) cppBinary).getDebuggerExecutableFile(), cppBinary.getTargetMachine().getArchitecture().getName());
target.setProductType(PBXTarget.ProductType.TOOL);
} else if (cppBinary instanceof CppSharedLibrary) {
target.addBinary(toBuildConfigurationName(component, cppBinary), ((CppSharedLibrary) cppBinary).getRuntimeFile(), cppBinary.getTargetMachine().getArchitecture().getName());
target.setProductType(PBXTarget.ProductType.DYNAMIC_LIBRARY);
} else if (cppBinary instanceof CppStaticLibrary) {
target.addBinary(toBuildConfigurationName(component, cppBinary), ((CppStaticLibrary) cppBinary).getLinkFile(), cppBinary.getTargetMachine().getArchitecture().getName());
target.setProductType(PBXTarget.ProductType.STATIC_LIBRARY);
}
if (cppBinary == component.getDevelopmentBinary().get()) {
target.getHeaderSearchPaths().from(component.getDevelopmentBinary().get().getCompileIncludePath());
target.getTaskDependencies().add(filterArtifactsFromImplicitBuilds(((DefaultCppBinary) component.getDevelopmentBinary().get()).getIncludePathConfiguration()).getBuildDependencies());
createSchemeTask(project.getTasks(), targetName, xcodeProject);
}
}
});
target.getHeaderSearchPaths().from(component.getPrivateHeaderDirs());
if (component instanceof CppLibrary) {
target.getHeaderSearchPaths().from(((CppLibrary) component).getPublicHeaderDirs());
}
xcodeProject.addTarget(target);
}
});
}
private static GenerateSchemeFileTask createSchemeTask(TaskContainer tasks, String schemeName, DefaultXcodeProject xcodeProject) {
// TODO - capitalise the target name in the task name
// TODO - don't create a launch target for a library
String name = "xcodeScheme";
GenerateSchemeFileTask schemeFileTask = tasks.maybeCreate(name, GenerateSchemeFileTask.class);
schemeFileTask.setXcodeProject(xcodeProject);
schemeFileTask.setOutputFile(new File(xcodeProject.getLocationDir(), "xcshareddata/xcschemes/" + schemeName + ".xcscheme"));
return schemeFileTask;
}
private XcodeTarget newTarget(String name, String productName, String gradleCommand, String taskName, FileCollection sources) {
String id = gidGenerator.generateGid("PBXLegacyTarget", name.hashCode());
XcodeTarget target = objectFactory.newInstance(XcodeTarget.class, name, id);
target.setTaskName(taskName);
target.setGradleCommand(gradleCommand);
target.setProductName(productName);
target.getSources().setFrom(sources);
return target;
}
private static class XcodeBridge implements Action {
private final DefaultXcodeProject xcodeProject;
private final Project project;
private final XcodePropertyAdapter xcodePropertyAdapter;
XcodeBridge(DefaultXcodeProject xcodeProject, Project project) {
this.xcodeProject = xcodeProject;
this.project = project;
this.xcodePropertyAdapter = new XcodePropertyAdapter(project);
}
@Override
public void execute(String taskName) {
if (taskName.startsWith("_xcode")) {
Task bridgeTask = project.getTasks().create(taskName);
String action = xcodePropertyAdapter.getAction();
if (action.equals("clean")) {
bridgeTask.dependsOn("clean");
} else if ("".equals(action) || "build".equals(action)) {
final XcodeTarget target = findXcodeTarget();
if (target.isUnitTest()) {
bridgeTestExecution(bridgeTask, target);
} else {
bridgeProductBuild(bridgeTask, target);
}
} else {
throw new GradleException("Unrecognized bridge action from Xcode '" + action + "'");
}
}
}
private XcodeTarget findXcodeTarget() {
final String productName = xcodePropertyAdapter.getProductName();
final XcodeTarget target = CollectionUtils.findFirst(xcodeProject.getTargets(), new Spec() {
@Override
public boolean isSatisfiedBy(XcodeTarget target) {
return target.getProductName().equals(productName);
}
});
if (target == null) {
throw new GradleException("Unknown Xcode target '" + productName + "', do you need to re-generate Xcode configuration?");
}
return target;
}
private void bridgeProductBuild(Task bridgeTask, XcodeTarget target) {
// Library or executable
final String configuration = xcodePropertyAdapter.getConfiguration();
bridgeTask.dependsOn(target.getBinaries().stream().filter(it -> it.getBuildConfigurationName().equals(configuration)).findFirst().get().getOutputFile());
}
private void bridgeTestExecution(Task bridgeTask, final XcodeTarget target) {
// XCTest executable
// Sync the binary to the BUILT_PRODUCTS_DIR, otherwise Xcode won't find any tests
final String builtProductsPath = xcodePropertyAdapter.getBuiltProductsDir();
final Sync syncTask = project.getTasks().create("syncBundleToXcodeBuiltProductDir", Sync.class, new Action() {
@Override
public void execute(Sync task) {
task.from(target.getDebugOutputFile());
task.into(builtProductsPath);
}
});
bridgeTask.dependsOn(syncTask);
}
}
private Action fromSourceDependency() {
return new Action() {
@Override
public void execute(ArtifactView.ViewConfiguration viewConfiguration) {
viewConfiguration.componentFilter(isSourceDependency());
}
};
}
private Spec isSourceDependency() {
return new Spec() {
@Override
public boolean isSatisfiedBy(ComponentIdentifier id) {
if (id instanceof ProjectComponentIdentifier) {
// Include as binary when the target project is not included in the workspace
return artifactRegistry.getIdeProject(XcodeProjectMetadata.class, (ProjectComponentIdentifier) id) == null;
}
return false;
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy