Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dependencyTargets #14

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/apple-targets/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ export type Config = {
*/
frameworks?: string[];

/**
* A list of additional extensions to add to the target (possibly from other targets, so match the name of the target),
* without the .appex extension.
* @example ["myWidgetExtension"]
*/
dependencyTargets?: string[];

/** Deployment iOS version for the target. Defaults to `16.4` */
deploymentTarget?: string;

Expand Down
53 changes: 46 additions & 7 deletions packages/apple-targets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,39 @@ import { sync as globSync } from "glob";
import path from "path";

import type { Config } from "./config";
import withWidget from "./withWidget";
import withWidget, { Props } from "./withWidget";
import { withXcodeProjectBetaBaseMod } from "./withXcparse";

// A target can depend on another target using the `dependencyTargets` property.
// Therefor, we need to execute the targets in the right order.
const sortTargetProps = (configs: Props[]) => {
const targetMap = new Map();
configs.forEach((target) => targetMap.set(target.name, target));

const visited = new Set();
const sorted: Props[] = [];

function visit(config: Props) {
if (visited.has(config.name)) {
return;
}
visited.add(config.name);

if (config.dependencyTargets) {
config.dependencyTargets.forEach((depName) => {
if (targetMap.has(depName)) {
visit(targetMap.get(depName));
}
});
}

sorted.push(config);
}

configs.forEach((target) => visit(target));
return sorted;
};

export const withTargetsDir: ConfigPlugin<{
appleTeamId: string;
match?: string;
Expand All @@ -22,12 +52,21 @@ export const withTargetsDir: ConfigPlugin<{
absolute: true,
});

targets.forEach((configPath) => {
config = withWidget(config, {
appleTeamId,
...require(configPath),
directory: path.relative(projectRoot, path.dirname(configPath)),
});
const targetProps = targets.map((configPath) => ({
appleTeamId,
...require(configPath),
directory: path.relative(projectRoot, path.dirname(configPath)),
}));

const sortedTargetProps = sortTargetProps(targetProps);

// Now we need to reverse the targets order. Thats because we will call withMod consecutively.
// When we call withMod#1 then withMod#2, the execution order of the mods will be withMod#2 then withMod#1.
// Thus we have to reverse …
sortedTargetProps.reverse();

sortedTargetProps.forEach((targetConfig) => {
config = withWidget(config, targetConfig);
});

return withXcodeProjectBetaBaseMod(config);
Expand Down
3 changes: 2 additions & 1 deletion packages/apple-targets/src/withWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getFrameworksForType, getTargetInfoPlistForType } from "./target";
import { withEASTargets } from "./withEasCredentials";
import { withXcodeChanges } from "./withXcodeChanges";

type Props = Config & {
export type Props = Config & {
directory: string;
};

Expand Down Expand Up @@ -140,6 +140,7 @@ const withWidget: ConfigPlugin<Props> = (config, props) => {
currentProjectVersion: config.ios?.buildNumber || 1,

frameworks: getFrameworksForType(props.type).concat(props.frameworks || []),
dependencyTargets: props.dependencyTargets || [],
type: props.type,
teamId: props.appleTeamId,
});
Expand Down
66 changes: 61 additions & 5 deletions packages/apple-targets/src/withXcodeChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
productTypeForType,
} from "./target";
import fixture from "./template/XCBuildConfiguration.json";
import { withXcodeProjectBeta } from "./withXcparse";
const TemplateBuildSettings = fixture as unknown as Record<
string,
{
Expand All @@ -37,7 +38,6 @@ const TemplateBuildSettings = fixture as unknown as Record<
info: any;
}
>;
import { withXcodeProjectBeta } from "./withXcparse";

export type XcodeSettings = {
name: string;
Expand All @@ -53,6 +53,8 @@ export type XcodeSettings = {

frameworks: string[];

dependencyTargets: string[];

type: ExtensionType;

hasAccentColor?: boolean;
Expand All @@ -68,9 +70,9 @@ export const withXcodeChanges: ConfigPlugin<XcodeSettings> = (
config,
props
) => {
return withXcodeProjectBeta(config, (config) => {
// @ts-ignore
applyXcodeChanges(config, config.modResults, props);
return withXcodeProjectBeta(config, async (config) => {
// NOTE: important to await here, so that withMods "wait" for another one to finish first
await applyXcodeChanges(config, config.modResults, props);
return config;
});
};
Expand Down Expand Up @@ -1017,7 +1019,7 @@ async function applyXcodeChanges(
})
);

let assetFiles = [
const assetFiles = [
// All assets`
// "assets/*",
// NOTE: Single-level only
Expand Down Expand Up @@ -1148,6 +1150,60 @@ async function applyXcodeChanges(
// Add the target dependency to the main app, should be only one.
mainAppTarget.props.dependencies.push(targetDependency);

// Check if we need to add target dependencies to the widgetTarget
props.dependencyTargets?.forEach((dependencyTarget) => {
const target = project.rootObject.props.targets.find(
(target) => target.props.productName === dependencyTarget
);

if (target) {
// On a target the productReference is a PBXFileReference, however, its not in the types currently, so we check for it:
let productReference: PBXFileReference;
if (
"productReference" in target.props &&
PBXFileReference.is(target.props.productReference)
) {
productReference = target.props.productReference;
} else {
throw new Error(
`You declared ${widgetTarget.props.productName} to depend on ${dependencyTarget}, but ${dependencyTarget} is invalid (missing appex reference) and can't be used!`
);
}

const containerItemProxy = PBXContainerItemProxy.create(project, {
containerPortal: project.rootObject,
proxyType: 1,
remoteGlobalIDString: target.uuid,
remoteInfo: dependencyTarget,
});
const targetDependency = PBXTargetDependency.create(project, {
target,
targetProxy: containerItemProxy,
});
widgetTarget.props.dependencies.push(targetDependency);

// We also need to add a build phase "Embed Foundation Extension" to the widget target
widgetTarget.createBuildPhase(PBXCopyFilesBuildPhase, {
dstSubfolderSpec: 13,
buildActionMask: 2147483647,
files: [
PBXBuildFile.create(project, {
fileRef: productReference,
settings: {
ATTRIBUTES: ["RemoveHeadersOnCopy"],
},
}),
],
name: "Embed Foundation Extensions",
runOnlyForDeploymentPostprocessing: 0,
});
} else {
console.warn(
`You declared ${widgetTarget.props.productName} to depend on ${dependencyTarget}, but ${dependencyTarget} couldn't be found in the project yet!`
);
}
});

const WELL_KNOWN_COPY_EXTENSIONS_NAME =
props.type === "clip"
? "Embed App Clips"
Expand Down