@cloudcare/cocos-sdk 0.1.0-alpha.4 → 0.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/bin/cli.cjs +11 -0
- package/dist/creator2/core/version.d.ts +1 -1
- package/dist/creator2/core/version.js +1 -1
- package/dist/creator3/core/version.d.ts +1 -1
- package/dist/creator3/core/version.js +1 -1
- package/extensions/creator2/install-native.cjs +73 -5
- package/extensions/creator2/install-spm.cjs +190 -0
- package/extensions/creator3/install-native.cjs +73 -5
- package/extensions/creator3/install-spm.cjs +190 -0
- package/native/ios/FTCocosBridge.m +8 -1
- package/native/ios/FTCocosBridge.podspec +3 -3
- package/native/ios/Package.swift +26 -0
- package/native/ios/include/FTCocosBridge.h +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,42 @@ Guance Cloud Application Monitoring collects and analyzes RUM, Log, Trace, and S
|
|
|
18
18
|
|
|
19
19
|
Cocos Creator 3.0–3.6.2 is supported on a best-effort basis because the stable native build extension API starts at 3.6.3. Web, mini-game, and desktop targets are not currently supported.
|
|
20
20
|
|
|
21
|
+
## iOS dependency manager
|
|
22
|
+
|
|
23
|
+
CocoaPods remains the default. To use Swift Package Manager, add
|
|
24
|
+
`cocos-sdk.config.json` to your **Cocos project root** (beside `assets`):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"ios": {
|
|
29
|
+
"dependencyManager": "spm"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Alternatively, save this setting when installing the extension:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npx @cloudcare/cocos-sdk install --ios-dependency-manager spm
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Rebuild the native iOS project in Creator after changing the configuration. The
|
|
41
|
+
extension links the local `FTCocosBridge` package and Xcode resolves the pinned
|
|
42
|
+
iOS SDK from Git. Fresh SPM projects do not require `pod install`; open the
|
|
43
|
+
`.xcodeproj`. If the host uses CocoaPods for other libraries, continue opening
|
|
44
|
+
its `.xcworkspace`.
|
|
45
|
+
|
|
46
|
+
When switching an existing installation, the extension removes its managed SDK
|
|
47
|
+
Pod entries and runs `pod install` if Pods were previously installed. Other Pods
|
|
48
|
+
are preserved. Manually declared SDK Pods or other Pods that depend on the same
|
|
49
|
+
native SDK must be migrated first to avoid duplicate linking. Set the value to
|
|
50
|
+
`cocoapods` to switch back, then run `pod install` as usual.
|
|
51
|
+
|
|
52
|
+
Creator 3 CMake regeneration restores the package integration automatically during
|
|
53
|
+
Xcode builds. If you regenerate the project by running CMake separately, rerun
|
|
54
|
+
Creator's native build integration before opening Xcode. Hybrid examples use the
|
|
55
|
+
same configuration with their existing `native:install` command.
|
|
56
|
+
|
|
21
57
|
## Examples
|
|
22
58
|
|
|
23
59
|
[Guance Cloud SDK Cocos Creator Demo](https://github.com/GuanceCloud/datakit-cocos/tree/main/examples)
|
package/bin/cli.cjs
CHANGED
|
@@ -28,6 +28,16 @@ const container = creatorMajor === 2 ? 'packages' : 'extensions';
|
|
|
28
28
|
const destination = path.join(projectRoot, container, 'guance-cocos-sdk');
|
|
29
29
|
const legacyDestination = path.join(projectRoot, container, 'ft-cocos-sdk');
|
|
30
30
|
const extensionSource = path.join(packageRoot, 'extensions', `creator${creatorMajor}`);
|
|
31
|
+
const manager = readOption(rawArguments, '--ios-dependency-manager');
|
|
32
|
+
if (manager !== undefined && !['spm', 'cocoapods'].includes(manager)) {
|
|
33
|
+
fail('Invalid --ios-dependency-manager. Expected spm or cocoapods.');
|
|
34
|
+
}
|
|
35
|
+
const configFile = path.join(projectRoot, 'cocos-sdk.config.json');
|
|
36
|
+
const config = readJson(configFile) || {};
|
|
37
|
+
if (manager !== undefined) {
|
|
38
|
+
config.ios = { ...config.ios, dependencyManager: manager };
|
|
39
|
+
fs.writeFileSync(configFile, `${JSON.stringify(config, null, 2)}\n`);
|
|
40
|
+
}
|
|
31
41
|
|
|
32
42
|
copyDirectory(extensionSource, destination);
|
|
33
43
|
copyDirectory(path.join(packageRoot, 'native'), path.join(destination, 'native'));
|
|
@@ -119,6 +129,7 @@ function printHelp() {
|
|
|
119
129
|
'Options:',
|
|
120
130
|
' --project <path> Cocos project root (defaults to the current directory)',
|
|
121
131
|
' --creator <2|3> Override automatic Cocos Creator version detection',
|
|
132
|
+
' --ios-dependency-manager <spm|cocoapods> Save the iOS dependency manager in cocos-sdk.config.json',
|
|
122
133
|
' -h, --help Show this help',
|
|
123
134
|
'',
|
|
124
135
|
].join('\n'));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Version of the installed Cocos SDK package. */
|
|
2
|
-
export declare const FT_COCOS_SDK_VERSION = "0.1.0-alpha.
|
|
2
|
+
export declare const FT_COCOS_SDK_VERSION = "0.1.0-alpha.5";
|
|
3
3
|
/** Global-context key used to report the Cocos SDK package version. */
|
|
4
4
|
export declare const FT_COCOS_SDK_VERSION_KEY = "sdk_package_cocos";
|
|
5
5
|
/** Event attribute used to identify data emitted through a framework bridge. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FT_COCOS_SDK_BRIDGE_INFO = exports.FT_COCOS_SDK_BRIDGE_INFO_KEY = exports.FT_COCOS_SDK_VERSION_KEY = exports.FT_COCOS_SDK_VERSION = void 0;
|
|
4
4
|
/** Version of the installed Cocos SDK package. */
|
|
5
|
-
exports.FT_COCOS_SDK_VERSION = '0.1.0-alpha.
|
|
5
|
+
exports.FT_COCOS_SDK_VERSION = '0.1.0-alpha.5';
|
|
6
6
|
/** Global-context key used to report the Cocos SDK package version. */
|
|
7
7
|
exports.FT_COCOS_SDK_VERSION_KEY = 'sdk_package_cocos';
|
|
8
8
|
/** Event attribute used to identify data emitted through a framework bridge. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Version of the installed Cocos SDK package. */
|
|
2
|
-
export declare const FT_COCOS_SDK_VERSION = "0.1.0-alpha.
|
|
2
|
+
export declare const FT_COCOS_SDK_VERSION = "0.1.0-alpha.5";
|
|
3
3
|
/** Global-context key used to report the Cocos SDK package version. */
|
|
4
4
|
export declare const FT_COCOS_SDK_VERSION_KEY = "sdk_package_cocos";
|
|
5
5
|
/** Event attribute used to identify data emitted through a framework bridge. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Version of the installed Cocos SDK package. */
|
|
2
|
-
export const FT_COCOS_SDK_VERSION = '0.1.0-alpha.
|
|
2
|
+
export const FT_COCOS_SDK_VERSION = '0.1.0-alpha.5';
|
|
3
3
|
/** Global-context key used to report the Cocos SDK package version. */
|
|
4
4
|
export const FT_COCOS_SDK_VERSION_KEY = 'sdk_package_cocos';
|
|
5
5
|
/** Event attribute used to identify data emitted through a framework bridge. */
|
|
@@ -22,6 +22,7 @@ function installNative(buildRoot, extensionRoot, logger = console) {
|
|
|
22
22
|
buildRoot = path.resolve(buildRoot);
|
|
23
23
|
extensionRoot = path.resolve(extensionRoot);
|
|
24
24
|
if (!fs.existsSync(buildRoot)) return;
|
|
25
|
+
const dependencyManager = readIosDependencyManager(buildRoot, extensionRoot);
|
|
25
26
|
const nativeSource = path.join(extensionRoot, 'native');
|
|
26
27
|
if (!fs.existsSync(nativeSource)) {
|
|
27
28
|
logger.warn(`[cocos-sdk] Native bridge not found at ${nativeSource}`);
|
|
@@ -47,7 +48,11 @@ function installNative(buildRoot, extensionRoot, logger = console) {
|
|
|
47
48
|
const iosApplicationProjects = xcodeProjects
|
|
48
49
|
.map((projectFile) => ({ projectFile, target: findIosApplicationTarget(projectFile) }))
|
|
49
50
|
.filter((project) => project.target);
|
|
50
|
-
if (
|
|
51
|
+
if (dependencyManager === 'spm' && iosApplicationProjects.length > 0) {
|
|
52
|
+
migrateManagedPods(podfiles, logger);
|
|
53
|
+
copyDirectory(path.join(nativeSource, 'ios'), path.join(buildNative, 'FTCocosBridge'));
|
|
54
|
+
}
|
|
55
|
+
if (dependencyManager === 'cocoapods' && podfiles.length === 0) {
|
|
51
56
|
iosApplicationProjects.forEach(({ projectFile, target }) => {
|
|
52
57
|
const projectDirectory = path.dirname(path.dirname(projectFile));
|
|
53
58
|
const podfile = path.join(projectDirectory, 'Podfile');
|
|
@@ -57,12 +62,17 @@ function installNative(buildRoot, extensionRoot, logger = console) {
|
|
|
57
62
|
}
|
|
58
63
|
iosApplicationProjects.forEach(({ projectFile, target }) => {
|
|
59
64
|
patchCocos2IosConfiguration(projectFile, target);
|
|
65
|
+
if (dependencyManager === 'spm') {
|
|
66
|
+
require('./install-spm.cjs').installSwiftPackage(projectFile, path.join(buildNative, 'FTCocosBridge'), 'FTCocosBridge', target);
|
|
67
|
+
} else if (fs.readFileSync(projectFile, 'utf8').includes('XCLocalSwiftPackageReference')) {
|
|
68
|
+
require('./install-spm.cjs').removeSwiftPackages(projectFile);
|
|
69
|
+
}
|
|
60
70
|
});
|
|
61
71
|
gradleFiles.forEach((file) => patchGradle(file, buildNative));
|
|
62
72
|
gradlePropertiesFiles.forEach(patchGradleProperties);
|
|
63
|
-
podfiles.forEach((file) => patchPodfile(file, buildNative));
|
|
73
|
+
if (dependencyManager === 'cocoapods') podfiles.forEach((file) => patchPodfile(file, buildNative));
|
|
64
74
|
logger.info(
|
|
65
|
-
`[cocos-sdk] Installed native bridge (${gradleFiles.length} Android, ${podfiles.length} iOS project files).`,
|
|
75
|
+
`[cocos-sdk] Installed native bridge (${gradleFiles.length} Android, ${dependencyManager === 'spm' ? iosApplicationProjects.length : podfiles.length} iOS project files; iOS: ${dependencyManager}).`,
|
|
66
76
|
);
|
|
67
77
|
}
|
|
68
78
|
|
|
@@ -252,7 +262,7 @@ function patchGradle(file, nativeRoot) {
|
|
|
252
262
|
" maven { url 'https://mvnrepo.guance.com/repository/maven-releases' }",
|
|
253
263
|
'}',
|
|
254
264
|
'dependencies {',
|
|
255
|
-
" implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-sdk:1.7.6-
|
|
265
|
+
" implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-sdk:1.7.6-alpha03'",
|
|
256
266
|
" implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-native:1.1.3'",
|
|
257
267
|
" implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-session-replay:0.1.9-alpha03'",
|
|
258
268
|
" implementation 'com.google.code.gson:gson:2.10.1'",
|
|
@@ -298,6 +308,10 @@ function replaceIndentedMarkedBlock(contents, begin, end, block) {
|
|
|
298
308
|
|
|
299
309
|
function findIosApplicationTarget(projectFile) {
|
|
300
310
|
const contents = fs.readFileSync(projectFile, 'utf8');
|
|
311
|
+
if (contents.includes('COCOS_SDK_XCODE_PROJECT')) {
|
|
312
|
+
const { readProject, applicationTarget } = require('./install-spm.cjs');
|
|
313
|
+
return applicationTarget(readProject(projectFile))?.[1].name || null;
|
|
314
|
+
}
|
|
301
315
|
const nativeTarget = /\/\* ([^*]+) \*\/ = \{\s*isa = PBXNativeTarget;([\s\S]*?)\n\s*\};/g;
|
|
302
316
|
const targets = [];
|
|
303
317
|
let match;
|
|
@@ -382,4 +396,58 @@ function escapeGroovy(value) { return value.replace(/\\/g, '\\\\').replace(/'/g,
|
|
|
382
396
|
function escapeRuby(value) { return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); }
|
|
383
397
|
function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
384
398
|
|
|
385
|
-
|
|
399
|
+
function readIosDependencyManager(buildRoot, extensionRoot) {
|
|
400
|
+
for (const start of [buildRoot, extensionRoot]) {
|
|
401
|
+
let directory = path.resolve(start);
|
|
402
|
+
while (true) {
|
|
403
|
+
const file = path.join(directory, 'cocos-sdk.config.json');
|
|
404
|
+
if (fs.existsSync(file)) {
|
|
405
|
+
const config = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
406
|
+
const manager = config.ios?.dependencyManager ?? 'cocoapods';
|
|
407
|
+
if (!['spm', 'cocoapods'].includes(manager)) {
|
|
408
|
+
throw new Error(`[cocos-sdk] Invalid ios.dependencyManager in ${file}: expected spm or cocoapods.`);
|
|
409
|
+
}
|
|
410
|
+
return manager;
|
|
411
|
+
}
|
|
412
|
+
const parent = path.dirname(directory);
|
|
413
|
+
if (parent === directory) break;
|
|
414
|
+
directory = parent;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return 'cocoapods';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function migrateManagedPods(podfiles, logger) {
|
|
421
|
+
for (const file of podfiles) {
|
|
422
|
+
const original = fs.readFileSync(file, 'utf8');
|
|
423
|
+
let next = original;
|
|
424
|
+
for (const [begin, end] of [
|
|
425
|
+
[POD_BEGIN, POD_END], [LEGACY_POD_BEGIN, LEGACY_POD_END], [LEGACY_BEGIN, LEGACY_END],
|
|
426
|
+
['# COCOS_HYBRID_SAMPLE_BEGIN', '# COCOS_HYBRID_SAMPLE_END'],
|
|
427
|
+
]) next = next.replace(markedPattern(begin, end), '');
|
|
428
|
+
if (/^\s*pod\s+['"](?:FTCocosBridge|HybridSampleHost|GuanceSDK)(?:\/[^'"]*)?['"]/m.test(next)) {
|
|
429
|
+
throw new Error(`[cocos-sdk] Remove the manually declared native SDK Pod from ${file} before switching to SPM.`);
|
|
430
|
+
}
|
|
431
|
+
if (next !== original) fs.writeFileSync(file, next);
|
|
432
|
+
const directory = path.dirname(file);
|
|
433
|
+
const existingLock = path.join(directory, 'Podfile.lock');
|
|
434
|
+
const hasSDKPods = fs.existsSync(existingLock)
|
|
435
|
+
&& /^\s*- (?:FTCocosBridge|HybridSampleHost|GuanceSDK)(?:\/|\s|:)/m.test(fs.readFileSync(existingLock, 'utf8'));
|
|
436
|
+
if (next === original && !hasSDKPods) continue;
|
|
437
|
+
if (fs.existsSync(path.join(directory, 'Podfile.lock')) || fs.existsSync(path.join(directory, 'Pods'))) {
|
|
438
|
+
logger.info('[cocos-sdk] Updating existing Pods integration for the SPM switch.');
|
|
439
|
+
try {
|
|
440
|
+
execFileSync('pod', ['install'], { cwd: directory, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 });
|
|
441
|
+
} catch (error) {
|
|
442
|
+
fs.writeFileSync(file, original);
|
|
443
|
+
throw new Error(`[cocos-sdk] Unable to update existing Pods in ${directory}: ${error.message}`);
|
|
444
|
+
}
|
|
445
|
+
const lock = path.join(directory, 'Podfile.lock');
|
|
446
|
+
if (fs.existsSync(lock) && /^\s*- GuanceSDK(?:\/|\s|:)/m.test(fs.readFileSync(lock, 'utf8'))) {
|
|
447
|
+
throw new Error(`[cocos-sdk] Another Pod still depends on the native SDK in ${lock}; migrate that dependency before using SPM.`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
module.exports = { installNative, readIosDependencyManager };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { createHash } = require('crypto');
|
|
6
|
+
const { execFileSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
function readProject(file) {
|
|
9
|
+
// plutil understands both Xcode's OpenStep format and XML plists, without Ruby/gems.
|
|
10
|
+
return JSON.parse(execFileSync('/usr/bin/plutil', ['-convert', 'json', '-o', '-', file], {
|
|
11
|
+
encoding: 'utf8', maxBuffer: 32 * 1024 * 1024,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writeProject(file, project) {
|
|
16
|
+
const next = `// !$*UTF8*$!\n// COCOS_SDK_XCODE_PROJECT\n${serialize(project)}\n`;
|
|
17
|
+
if (fs.readFileSync(file, 'utf8') !== next) fs.writeFileSync(file, next);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function serialize(value, depth = 0) {
|
|
21
|
+
const indent = '\t'.repeat(depth);
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return `(\n${value.map((entry) => `${indent}\t${serialize(entry, depth + 1)},\n`).join('')}${indent})`;
|
|
24
|
+
}
|
|
25
|
+
if (value && typeof value === 'object') {
|
|
26
|
+
return `{\n${Object.entries(value).sort(([a], [b]) => a === 'isa' ? -1 : b === 'isa' ? 1 : a.localeCompare(b)).map(([key, entry]) => (
|
|
27
|
+
`${indent}\t${serialize(key)} = ${serialize(entry, depth + 1)};\n`
|
|
28
|
+
)).join('')}${indent}}`;
|
|
29
|
+
}
|
|
30
|
+
const text = String(value);
|
|
31
|
+
return /^[A-Za-z0-9_./]+$/.test(text) ? text : JSON.stringify(text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function applicationTarget(project, preferredName) {
|
|
35
|
+
const targets = Object.entries(project.objects).filter(([, object]) => (
|
|
36
|
+
object.isa === 'PBXNativeTarget' && object.productType === 'com.apple.product-type.application'
|
|
37
|
+
));
|
|
38
|
+
return targets.find(([, object]) => object.name === preferredName)
|
|
39
|
+
|| targets.find(([, object]) => /(?:^|[-_.])(?:mobile|ios)$/i.test(object.name))
|
|
40
|
+
|| targets[0];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function add(list, value) {
|
|
44
|
+
if (!list.includes(value)) list.push(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function identifier(value) {
|
|
48
|
+
return createHash('sha256').update(`cocos-sdk:${value}`).digest('hex').slice(0, 24).toUpperCase();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function installSwiftPackage(projectFile, packageDirectory, productName, targetName) {
|
|
52
|
+
if (!fs.existsSync(path.join(packageDirectory, 'Package.swift'))) {
|
|
53
|
+
throw new Error(`[cocos-sdk] Missing Swift package: ${packageDirectory}`);
|
|
54
|
+
}
|
|
55
|
+
const project = readProject(projectFile);
|
|
56
|
+
const selected = applicationTarget(project, targetName);
|
|
57
|
+
if (!selected) throw new Error(`[cocos-sdk] No application target in ${projectFile}`);
|
|
58
|
+
const [targetID, target] = selected;
|
|
59
|
+
const objects = project.objects;
|
|
60
|
+
const root = objects[project.rootObject];
|
|
61
|
+
const projectDirectory = path.resolve(path.dirname(path.dirname(projectFile)), root.projectDirPath || '.');
|
|
62
|
+
const relativePath = path.relative(projectDirectory, packageDirectory).split(path.sep).join('/');
|
|
63
|
+
const packageID = identifier(`package:${productName}`);
|
|
64
|
+
const productID = identifier(`product:${targetID}:${productName}`);
|
|
65
|
+
const buildID = identifier(`framework:${targetID}:${productName}`);
|
|
66
|
+
const existing = (target.packageProductDependencies || []).find((id) => (
|
|
67
|
+
id !== productID && objects[id]?.productName === productName
|
|
68
|
+
));
|
|
69
|
+
if (existing) throw new Error(`[cocos-sdk] ${productName} is already managed manually in ${target.name}; remove that reference before automatic installation.`);
|
|
70
|
+
|
|
71
|
+
objects[packageID] = { isa: 'XCLocalSwiftPackageReference', relativePath };
|
|
72
|
+
objects[productID] = { isa: 'XCSwiftPackageProductDependency', productName };
|
|
73
|
+
objects[buildID] = { isa: 'PBXBuildFile', productRef: productID };
|
|
74
|
+
add(root.packageReferences ||= [], packageID);
|
|
75
|
+
add(target.packageProductDependencies ||= [], productID);
|
|
76
|
+
let phaseID = (target.buildPhases || []).find((id) => objects[id]?.isa === 'PBXFrameworksBuildPhase');
|
|
77
|
+
if (!phaseID) {
|
|
78
|
+
phaseID = identifier(`frameworks:${targetID}`);
|
|
79
|
+
objects[phaseID] = { isa: 'PBXFrameworksBuildPhase', buildActionMask: '2147483647', files: [], runOnlyForDeploymentPostprocessing: '0' };
|
|
80
|
+
add(target.buildPhases ||= [], phaseID);
|
|
81
|
+
}
|
|
82
|
+
add(objects[phaseID].files ||= [], buildID);
|
|
83
|
+
const configurations = objects[target.buildConfigurationList]?.buildConfigurations || [];
|
|
84
|
+
for (const configID of configurations) {
|
|
85
|
+
const settings = objects[configID].buildSettings ||= {};
|
|
86
|
+
const flags = settings.OTHER_LDFLAGS;
|
|
87
|
+
if (Array.isArray(flags)) {
|
|
88
|
+
add(flags, '$(inherited)');
|
|
89
|
+
add(flags, '-ObjC');
|
|
90
|
+
} else {
|
|
91
|
+
const parts = [flags || '$(inherited)'];
|
|
92
|
+
if (!String(flags || '').includes('$(inherited)')) add(parts, '$(inherited)');
|
|
93
|
+
if (!/(?:^|\s)-ObjC(?:\s|$)/.test(flags || '')) add(parts, '-ObjC');
|
|
94
|
+
settings.OTHER_LDFLAGS = parts.join(' ');
|
|
95
|
+
}
|
|
96
|
+
if (Number(settings.IPHONEOS_DEPLOYMENT_TARGET || 0) < 12) settings.IPHONEOS_DEPLOYMENT_TARGET = '12.0';
|
|
97
|
+
settings.CLANG_ENABLE_MODULES = 'YES';
|
|
98
|
+
}
|
|
99
|
+
preserveCmakeIntegration(projectFile, project, { packageDirectory, productName, targetName: target.name });
|
|
100
|
+
writeProject(projectFile, project);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const RESTORE_BEGIN = '# COCOS_SDK_SPM_RESTORE_BEGIN';
|
|
104
|
+
const RESTORE_END = '# COCOS_SDK_SPM_RESTORE_END';
|
|
105
|
+
const restorePattern = /\n?# COCOS_SDK_SPM_RESTORE_BEGIN[\s\S]*?# COCOS_SDK_SPM_RESTORE_END\n?/g;
|
|
106
|
+
|
|
107
|
+
function shellQuote(value) {
|
|
108
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function preserveCmakeIntegration(projectFile, project, entry) {
|
|
112
|
+
const objects = project.objects;
|
|
113
|
+
const zeroCheck = Object.values(objects).find((object) => object.name === 'ZERO_CHECK');
|
|
114
|
+
if (!zeroCheck) return;
|
|
115
|
+
// CMake's SYMROOT/OBJROOT overrides select Xcode's legacy build locations,
|
|
116
|
+
// which reject packages. Keep CONFIGURATION_BUILD_DIR (engine archives).
|
|
117
|
+
const stateFile = path.join(path.dirname(projectFile), 'cocos-sdk-spm.json');
|
|
118
|
+
const state = fs.existsSync(stateFile)
|
|
119
|
+
? JSON.parse(fs.readFileSync(stateFile, 'utf8')) : { packages: [], buildLocations: {} };
|
|
120
|
+
for (const [id, object] of Object.entries(objects)) {
|
|
121
|
+
if (object.isa !== 'XCBuildConfiguration') continue;
|
|
122
|
+
for (const key of ['SYMROOT', 'OBJROOT']) {
|
|
123
|
+
if (object.buildSettings?.[key] === undefined) continue;
|
|
124
|
+
(state.buildLocations[id] ||= {})[key] = object.buildSettings[key];
|
|
125
|
+
delete object.buildSettings[key];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const relativeDirectory = path.relative(path.dirname(projectFile), entry.packageDirectory);
|
|
129
|
+
state.packages = state.packages.filter((item) => item.productName !== entry.productName);
|
|
130
|
+
state.packages.push({ ...entry, packageDirectory: relativeDirectory });
|
|
131
|
+
state.packages.sort((a, b) => a.productName.localeCompare(b.productName));
|
|
132
|
+
const helper = path.join(path.dirname(projectFile), 'cocos-sdk-spm.cjs');
|
|
133
|
+
if (path.resolve(helper) !== path.resolve(__filename)) fs.copyFileSync(__filename, helper);
|
|
134
|
+
const restore = [
|
|
135
|
+
RESTORE_BEGIN,
|
|
136
|
+
`${process.versions.electron ? 'ELECTRON_RUN_AS_NODE=1 ' : ''}${shellQuote(process.execPath)} ${shellQuote(helper)} --restore`,
|
|
137
|
+
RESTORE_END,
|
|
138
|
+
].join('\n');
|
|
139
|
+
for (const id of zeroCheck.buildPhases || []) {
|
|
140
|
+
const phase = objects[id];
|
|
141
|
+
if (phase?.isa !== 'PBXShellScriptBuildPhase') continue;
|
|
142
|
+
phase.shellScript = `${(phase.shellScript || '').replace(restorePattern, '').trimEnd()}\n${restore}\n`;
|
|
143
|
+
}
|
|
144
|
+
const serialized = `${JSON.stringify(state, null, 2)}\n`;
|
|
145
|
+
if (!fs.existsSync(stateFile) || fs.readFileSync(stateFile, 'utf8') !== serialized) fs.writeFileSync(stateFile, serialized);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function removeSwiftPackages(projectFile) {
|
|
149
|
+
const project = readProject(projectFile);
|
|
150
|
+
const objects = project.objects;
|
|
151
|
+
const removed = new Set();
|
|
152
|
+
for (const productName of ['FTCocosBridge', 'HybridSampleHost']) {
|
|
153
|
+
const packageID = identifier(`package:${productName}`);
|
|
154
|
+
if (!objects[packageID]) continue;
|
|
155
|
+
removed.add(packageID);
|
|
156
|
+
for (const [id, object] of Object.entries(objects)) {
|
|
157
|
+
if (object.isa !== 'PBXNativeTarget') continue;
|
|
158
|
+
removed.add(identifier(`product:${id}:${productName}`));
|
|
159
|
+
removed.add(identifier(`framework:${id}:${productName}`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!removed.size) return;
|
|
163
|
+
for (const id of removed) delete objects[id];
|
|
164
|
+
for (const object of Object.values(objects)) {
|
|
165
|
+
for (const key of ['packageReferences', 'packageProductDependencies', 'files']) {
|
|
166
|
+
if (Array.isArray(object[key])) object[key] = object[key].filter((id) => !removed.has(id));
|
|
167
|
+
}
|
|
168
|
+
if (object.isa === 'PBXShellScriptBuildPhase') object.shellScript = (object.shellScript || '').replace(restorePattern, '');
|
|
169
|
+
}
|
|
170
|
+
const stateFile = path.join(path.dirname(projectFile), 'cocos-sdk-spm.json');
|
|
171
|
+
if (fs.existsSync(stateFile)) {
|
|
172
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
173
|
+
for (const [id, settings] of Object.entries(state.buildLocations)) {
|
|
174
|
+
if (objects[id]) Object.assign(objects[id].buildSettings ||= {}, settings);
|
|
175
|
+
}
|
|
176
|
+
fs.unlinkSync(stateFile);
|
|
177
|
+
fs.rmSync(path.join(path.dirname(projectFile), 'cocos-sdk-spm.cjs'), { force: true });
|
|
178
|
+
}
|
|
179
|
+
writeProject(projectFile, project);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = { installSwiftPackage, removeSwiftPackages, readProject, applicationTarget };
|
|
183
|
+
|
|
184
|
+
if (require.main === module && process.argv[2] === '--restore') {
|
|
185
|
+
const directory = path.dirname(__filename);
|
|
186
|
+
const state = JSON.parse(fs.readFileSync(path.join(directory, 'cocos-sdk-spm.json'), 'utf8'));
|
|
187
|
+
for (const item of state.packages) {
|
|
188
|
+
installSwiftPackage(path.join(directory, 'project.pbxproj'), path.resolve(directory, item.packageDirectory), item.productName, item.targetName);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -22,6 +22,7 @@ function installNative(buildRoot, extensionRoot, logger = console) {
|
|
|
22
22
|
buildRoot = path.resolve(buildRoot);
|
|
23
23
|
extensionRoot = path.resolve(extensionRoot);
|
|
24
24
|
if (!fs.existsSync(buildRoot)) return;
|
|
25
|
+
const dependencyManager = readIosDependencyManager(buildRoot, extensionRoot);
|
|
25
26
|
const nativeSource = path.join(extensionRoot, 'native');
|
|
26
27
|
if (!fs.existsSync(nativeSource)) {
|
|
27
28
|
logger.warn(`[cocos-sdk] Native bridge not found at ${nativeSource}`);
|
|
@@ -47,7 +48,11 @@ function installNative(buildRoot, extensionRoot, logger = console) {
|
|
|
47
48
|
const iosApplicationProjects = xcodeProjects
|
|
48
49
|
.map((projectFile) => ({ projectFile, target: findIosApplicationTarget(projectFile) }))
|
|
49
50
|
.filter((project) => project.target);
|
|
50
|
-
if (
|
|
51
|
+
if (dependencyManager === 'spm' && iosApplicationProjects.length > 0) {
|
|
52
|
+
migrateManagedPods(podfiles, logger);
|
|
53
|
+
copyDirectory(path.join(nativeSource, 'ios'), path.join(buildNative, 'FTCocosBridge'));
|
|
54
|
+
}
|
|
55
|
+
if (dependencyManager === 'cocoapods' && podfiles.length === 0) {
|
|
51
56
|
iosApplicationProjects.forEach(({ projectFile, target }) => {
|
|
52
57
|
const projectDirectory = path.dirname(path.dirname(projectFile));
|
|
53
58
|
const podfile = path.join(projectDirectory, 'Podfile');
|
|
@@ -57,12 +62,17 @@ function installNative(buildRoot, extensionRoot, logger = console) {
|
|
|
57
62
|
}
|
|
58
63
|
iosApplicationProjects.forEach(({ projectFile, target }) => {
|
|
59
64
|
patchCocos2IosConfiguration(projectFile, target);
|
|
65
|
+
if (dependencyManager === 'spm') {
|
|
66
|
+
require('./install-spm.cjs').installSwiftPackage(projectFile, path.join(buildNative, 'FTCocosBridge'), 'FTCocosBridge', target);
|
|
67
|
+
} else if (fs.readFileSync(projectFile, 'utf8').includes('XCLocalSwiftPackageReference')) {
|
|
68
|
+
require('./install-spm.cjs').removeSwiftPackages(projectFile);
|
|
69
|
+
}
|
|
60
70
|
});
|
|
61
71
|
gradleFiles.forEach((file) => patchGradle(file, buildNative));
|
|
62
72
|
gradlePropertiesFiles.forEach(patchGradleProperties);
|
|
63
|
-
podfiles.forEach((file) => patchPodfile(file, buildNative));
|
|
73
|
+
if (dependencyManager === 'cocoapods') podfiles.forEach((file) => patchPodfile(file, buildNative));
|
|
64
74
|
logger.info(
|
|
65
|
-
`[cocos-sdk] Installed native bridge (${gradleFiles.length} Android, ${podfiles.length} iOS project files).`,
|
|
75
|
+
`[cocos-sdk] Installed native bridge (${gradleFiles.length} Android, ${dependencyManager === 'spm' ? iosApplicationProjects.length : podfiles.length} iOS project files; iOS: ${dependencyManager}).`,
|
|
66
76
|
);
|
|
67
77
|
}
|
|
68
78
|
|
|
@@ -252,7 +262,7 @@ function patchGradle(file, nativeRoot) {
|
|
|
252
262
|
" maven { url 'https://mvnrepo.guance.com/repository/maven-releases' }",
|
|
253
263
|
'}',
|
|
254
264
|
'dependencies {',
|
|
255
|
-
" implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-sdk:1.7.6-
|
|
265
|
+
" implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-sdk:1.7.6-alpha03'",
|
|
256
266
|
" implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-native:1.1.3'",
|
|
257
267
|
" implementation 'com.cloudcare.ft.mobile.sdk.tracker.agent:ft-session-replay:0.1.9-alpha03'",
|
|
258
268
|
" implementation 'com.google.code.gson:gson:2.10.1'",
|
|
@@ -298,6 +308,10 @@ function replaceIndentedMarkedBlock(contents, begin, end, block) {
|
|
|
298
308
|
|
|
299
309
|
function findIosApplicationTarget(projectFile) {
|
|
300
310
|
const contents = fs.readFileSync(projectFile, 'utf8');
|
|
311
|
+
if (contents.includes('COCOS_SDK_XCODE_PROJECT')) {
|
|
312
|
+
const { readProject, applicationTarget } = require('./install-spm.cjs');
|
|
313
|
+
return applicationTarget(readProject(projectFile))?.[1].name || null;
|
|
314
|
+
}
|
|
301
315
|
const nativeTarget = /\/\* ([^*]+) \*\/ = \{\s*isa = PBXNativeTarget;([\s\S]*?)\n\s*\};/g;
|
|
302
316
|
const targets = [];
|
|
303
317
|
let match;
|
|
@@ -382,4 +396,58 @@ function escapeGroovy(value) { return value.replace(/\\/g, '\\\\').replace(/'/g,
|
|
|
382
396
|
function escapeRuby(value) { return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); }
|
|
383
397
|
function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
384
398
|
|
|
385
|
-
|
|
399
|
+
function readIosDependencyManager(buildRoot, extensionRoot) {
|
|
400
|
+
for (const start of [buildRoot, extensionRoot]) {
|
|
401
|
+
let directory = path.resolve(start);
|
|
402
|
+
while (true) {
|
|
403
|
+
const file = path.join(directory, 'cocos-sdk.config.json');
|
|
404
|
+
if (fs.existsSync(file)) {
|
|
405
|
+
const config = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
406
|
+
const manager = config.ios?.dependencyManager ?? 'cocoapods';
|
|
407
|
+
if (!['spm', 'cocoapods'].includes(manager)) {
|
|
408
|
+
throw new Error(`[cocos-sdk] Invalid ios.dependencyManager in ${file}: expected spm or cocoapods.`);
|
|
409
|
+
}
|
|
410
|
+
return manager;
|
|
411
|
+
}
|
|
412
|
+
const parent = path.dirname(directory);
|
|
413
|
+
if (parent === directory) break;
|
|
414
|
+
directory = parent;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return 'cocoapods';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function migrateManagedPods(podfiles, logger) {
|
|
421
|
+
for (const file of podfiles) {
|
|
422
|
+
const original = fs.readFileSync(file, 'utf8');
|
|
423
|
+
let next = original;
|
|
424
|
+
for (const [begin, end] of [
|
|
425
|
+
[POD_BEGIN, POD_END], [LEGACY_POD_BEGIN, LEGACY_POD_END], [LEGACY_BEGIN, LEGACY_END],
|
|
426
|
+
['# COCOS_HYBRID_SAMPLE_BEGIN', '# COCOS_HYBRID_SAMPLE_END'],
|
|
427
|
+
]) next = next.replace(markedPattern(begin, end), '');
|
|
428
|
+
if (/^\s*pod\s+['"](?:FTCocosBridge|HybridSampleHost|GuanceSDK)(?:\/[^'"]*)?['"]/m.test(next)) {
|
|
429
|
+
throw new Error(`[cocos-sdk] Remove the manually declared native SDK Pod from ${file} before switching to SPM.`);
|
|
430
|
+
}
|
|
431
|
+
if (next !== original) fs.writeFileSync(file, next);
|
|
432
|
+
const directory = path.dirname(file);
|
|
433
|
+
const existingLock = path.join(directory, 'Podfile.lock');
|
|
434
|
+
const hasSDKPods = fs.existsSync(existingLock)
|
|
435
|
+
&& /^\s*- (?:FTCocosBridge|HybridSampleHost|GuanceSDK)(?:\/|\s|:)/m.test(fs.readFileSync(existingLock, 'utf8'));
|
|
436
|
+
if (next === original && !hasSDKPods) continue;
|
|
437
|
+
if (fs.existsSync(path.join(directory, 'Podfile.lock')) || fs.existsSync(path.join(directory, 'Pods'))) {
|
|
438
|
+
logger.info('[cocos-sdk] Updating existing Pods integration for the SPM switch.');
|
|
439
|
+
try {
|
|
440
|
+
execFileSync('pod', ['install'], { cwd: directory, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 });
|
|
441
|
+
} catch (error) {
|
|
442
|
+
fs.writeFileSync(file, original);
|
|
443
|
+
throw new Error(`[cocos-sdk] Unable to update existing Pods in ${directory}: ${error.message}`);
|
|
444
|
+
}
|
|
445
|
+
const lock = path.join(directory, 'Podfile.lock');
|
|
446
|
+
if (fs.existsSync(lock) && /^\s*- GuanceSDK(?:\/|\s|:)/m.test(fs.readFileSync(lock, 'utf8'))) {
|
|
447
|
+
throw new Error(`[cocos-sdk] Another Pod still depends on the native SDK in ${lock}; migrate that dependency before using SPM.`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
module.exports = { installNative, readIosDependencyManager };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { createHash } = require('crypto');
|
|
6
|
+
const { execFileSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
function readProject(file) {
|
|
9
|
+
// plutil understands both Xcode's OpenStep format and XML plists, without Ruby/gems.
|
|
10
|
+
return JSON.parse(execFileSync('/usr/bin/plutil', ['-convert', 'json', '-o', '-', file], {
|
|
11
|
+
encoding: 'utf8', maxBuffer: 32 * 1024 * 1024,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writeProject(file, project) {
|
|
16
|
+
const next = `// !$*UTF8*$!\n// COCOS_SDK_XCODE_PROJECT\n${serialize(project)}\n`;
|
|
17
|
+
if (fs.readFileSync(file, 'utf8') !== next) fs.writeFileSync(file, next);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function serialize(value, depth = 0) {
|
|
21
|
+
const indent = '\t'.repeat(depth);
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return `(\n${value.map((entry) => `${indent}\t${serialize(entry, depth + 1)},\n`).join('')}${indent})`;
|
|
24
|
+
}
|
|
25
|
+
if (value && typeof value === 'object') {
|
|
26
|
+
return `{\n${Object.entries(value).sort(([a], [b]) => a === 'isa' ? -1 : b === 'isa' ? 1 : a.localeCompare(b)).map(([key, entry]) => (
|
|
27
|
+
`${indent}\t${serialize(key)} = ${serialize(entry, depth + 1)};\n`
|
|
28
|
+
)).join('')}${indent}}`;
|
|
29
|
+
}
|
|
30
|
+
const text = String(value);
|
|
31
|
+
return /^[A-Za-z0-9_./]+$/.test(text) ? text : JSON.stringify(text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function applicationTarget(project, preferredName) {
|
|
35
|
+
const targets = Object.entries(project.objects).filter(([, object]) => (
|
|
36
|
+
object.isa === 'PBXNativeTarget' && object.productType === 'com.apple.product-type.application'
|
|
37
|
+
));
|
|
38
|
+
return targets.find(([, object]) => object.name === preferredName)
|
|
39
|
+
|| targets.find(([, object]) => /(?:^|[-_.])(?:mobile|ios)$/i.test(object.name))
|
|
40
|
+
|| targets[0];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function add(list, value) {
|
|
44
|
+
if (!list.includes(value)) list.push(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function identifier(value) {
|
|
48
|
+
return createHash('sha256').update(`cocos-sdk:${value}`).digest('hex').slice(0, 24).toUpperCase();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function installSwiftPackage(projectFile, packageDirectory, productName, targetName) {
|
|
52
|
+
if (!fs.existsSync(path.join(packageDirectory, 'Package.swift'))) {
|
|
53
|
+
throw new Error(`[cocos-sdk] Missing Swift package: ${packageDirectory}`);
|
|
54
|
+
}
|
|
55
|
+
const project = readProject(projectFile);
|
|
56
|
+
const selected = applicationTarget(project, targetName);
|
|
57
|
+
if (!selected) throw new Error(`[cocos-sdk] No application target in ${projectFile}`);
|
|
58
|
+
const [targetID, target] = selected;
|
|
59
|
+
const objects = project.objects;
|
|
60
|
+
const root = objects[project.rootObject];
|
|
61
|
+
const projectDirectory = path.resolve(path.dirname(path.dirname(projectFile)), root.projectDirPath || '.');
|
|
62
|
+
const relativePath = path.relative(projectDirectory, packageDirectory).split(path.sep).join('/');
|
|
63
|
+
const packageID = identifier(`package:${productName}`);
|
|
64
|
+
const productID = identifier(`product:${targetID}:${productName}`);
|
|
65
|
+
const buildID = identifier(`framework:${targetID}:${productName}`);
|
|
66
|
+
const existing = (target.packageProductDependencies || []).find((id) => (
|
|
67
|
+
id !== productID && objects[id]?.productName === productName
|
|
68
|
+
));
|
|
69
|
+
if (existing) throw new Error(`[cocos-sdk] ${productName} is already managed manually in ${target.name}; remove that reference before automatic installation.`);
|
|
70
|
+
|
|
71
|
+
objects[packageID] = { isa: 'XCLocalSwiftPackageReference', relativePath };
|
|
72
|
+
objects[productID] = { isa: 'XCSwiftPackageProductDependency', productName };
|
|
73
|
+
objects[buildID] = { isa: 'PBXBuildFile', productRef: productID };
|
|
74
|
+
add(root.packageReferences ||= [], packageID);
|
|
75
|
+
add(target.packageProductDependencies ||= [], productID);
|
|
76
|
+
let phaseID = (target.buildPhases || []).find((id) => objects[id]?.isa === 'PBXFrameworksBuildPhase');
|
|
77
|
+
if (!phaseID) {
|
|
78
|
+
phaseID = identifier(`frameworks:${targetID}`);
|
|
79
|
+
objects[phaseID] = { isa: 'PBXFrameworksBuildPhase', buildActionMask: '2147483647', files: [], runOnlyForDeploymentPostprocessing: '0' };
|
|
80
|
+
add(target.buildPhases ||= [], phaseID);
|
|
81
|
+
}
|
|
82
|
+
add(objects[phaseID].files ||= [], buildID);
|
|
83
|
+
const configurations = objects[target.buildConfigurationList]?.buildConfigurations || [];
|
|
84
|
+
for (const configID of configurations) {
|
|
85
|
+
const settings = objects[configID].buildSettings ||= {};
|
|
86
|
+
const flags = settings.OTHER_LDFLAGS;
|
|
87
|
+
if (Array.isArray(flags)) {
|
|
88
|
+
add(flags, '$(inherited)');
|
|
89
|
+
add(flags, '-ObjC');
|
|
90
|
+
} else {
|
|
91
|
+
const parts = [flags || '$(inherited)'];
|
|
92
|
+
if (!String(flags || '').includes('$(inherited)')) add(parts, '$(inherited)');
|
|
93
|
+
if (!/(?:^|\s)-ObjC(?:\s|$)/.test(flags || '')) add(parts, '-ObjC');
|
|
94
|
+
settings.OTHER_LDFLAGS = parts.join(' ');
|
|
95
|
+
}
|
|
96
|
+
if (Number(settings.IPHONEOS_DEPLOYMENT_TARGET || 0) < 12) settings.IPHONEOS_DEPLOYMENT_TARGET = '12.0';
|
|
97
|
+
settings.CLANG_ENABLE_MODULES = 'YES';
|
|
98
|
+
}
|
|
99
|
+
preserveCmakeIntegration(projectFile, project, { packageDirectory, productName, targetName: target.name });
|
|
100
|
+
writeProject(projectFile, project);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const RESTORE_BEGIN = '# COCOS_SDK_SPM_RESTORE_BEGIN';
|
|
104
|
+
const RESTORE_END = '# COCOS_SDK_SPM_RESTORE_END';
|
|
105
|
+
const restorePattern = /\n?# COCOS_SDK_SPM_RESTORE_BEGIN[\s\S]*?# COCOS_SDK_SPM_RESTORE_END\n?/g;
|
|
106
|
+
|
|
107
|
+
function shellQuote(value) {
|
|
108
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function preserveCmakeIntegration(projectFile, project, entry) {
|
|
112
|
+
const objects = project.objects;
|
|
113
|
+
const zeroCheck = Object.values(objects).find((object) => object.name === 'ZERO_CHECK');
|
|
114
|
+
if (!zeroCheck) return;
|
|
115
|
+
// CMake's SYMROOT/OBJROOT overrides select Xcode's legacy build locations,
|
|
116
|
+
// which reject packages. Keep CONFIGURATION_BUILD_DIR (engine archives).
|
|
117
|
+
const stateFile = path.join(path.dirname(projectFile), 'cocos-sdk-spm.json');
|
|
118
|
+
const state = fs.existsSync(stateFile)
|
|
119
|
+
? JSON.parse(fs.readFileSync(stateFile, 'utf8')) : { packages: [], buildLocations: {} };
|
|
120
|
+
for (const [id, object] of Object.entries(objects)) {
|
|
121
|
+
if (object.isa !== 'XCBuildConfiguration') continue;
|
|
122
|
+
for (const key of ['SYMROOT', 'OBJROOT']) {
|
|
123
|
+
if (object.buildSettings?.[key] === undefined) continue;
|
|
124
|
+
(state.buildLocations[id] ||= {})[key] = object.buildSettings[key];
|
|
125
|
+
delete object.buildSettings[key];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const relativeDirectory = path.relative(path.dirname(projectFile), entry.packageDirectory);
|
|
129
|
+
state.packages = state.packages.filter((item) => item.productName !== entry.productName);
|
|
130
|
+
state.packages.push({ ...entry, packageDirectory: relativeDirectory });
|
|
131
|
+
state.packages.sort((a, b) => a.productName.localeCompare(b.productName));
|
|
132
|
+
const helper = path.join(path.dirname(projectFile), 'cocos-sdk-spm.cjs');
|
|
133
|
+
if (path.resolve(helper) !== path.resolve(__filename)) fs.copyFileSync(__filename, helper);
|
|
134
|
+
const restore = [
|
|
135
|
+
RESTORE_BEGIN,
|
|
136
|
+
`${process.versions.electron ? 'ELECTRON_RUN_AS_NODE=1 ' : ''}${shellQuote(process.execPath)} ${shellQuote(helper)} --restore`,
|
|
137
|
+
RESTORE_END,
|
|
138
|
+
].join('\n');
|
|
139
|
+
for (const id of zeroCheck.buildPhases || []) {
|
|
140
|
+
const phase = objects[id];
|
|
141
|
+
if (phase?.isa !== 'PBXShellScriptBuildPhase') continue;
|
|
142
|
+
phase.shellScript = `${(phase.shellScript || '').replace(restorePattern, '').trimEnd()}\n${restore}\n`;
|
|
143
|
+
}
|
|
144
|
+
const serialized = `${JSON.stringify(state, null, 2)}\n`;
|
|
145
|
+
if (!fs.existsSync(stateFile) || fs.readFileSync(stateFile, 'utf8') !== serialized) fs.writeFileSync(stateFile, serialized);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function removeSwiftPackages(projectFile) {
|
|
149
|
+
const project = readProject(projectFile);
|
|
150
|
+
const objects = project.objects;
|
|
151
|
+
const removed = new Set();
|
|
152
|
+
for (const productName of ['FTCocosBridge', 'HybridSampleHost']) {
|
|
153
|
+
const packageID = identifier(`package:${productName}`);
|
|
154
|
+
if (!objects[packageID]) continue;
|
|
155
|
+
removed.add(packageID);
|
|
156
|
+
for (const [id, object] of Object.entries(objects)) {
|
|
157
|
+
if (object.isa !== 'PBXNativeTarget') continue;
|
|
158
|
+
removed.add(identifier(`product:${id}:${productName}`));
|
|
159
|
+
removed.add(identifier(`framework:${id}:${productName}`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!removed.size) return;
|
|
163
|
+
for (const id of removed) delete objects[id];
|
|
164
|
+
for (const object of Object.values(objects)) {
|
|
165
|
+
for (const key of ['packageReferences', 'packageProductDependencies', 'files']) {
|
|
166
|
+
if (Array.isArray(object[key])) object[key] = object[key].filter((id) => !removed.has(id));
|
|
167
|
+
}
|
|
168
|
+
if (object.isa === 'PBXShellScriptBuildPhase') object.shellScript = (object.shellScript || '').replace(restorePattern, '');
|
|
169
|
+
}
|
|
170
|
+
const stateFile = path.join(path.dirname(projectFile), 'cocos-sdk-spm.json');
|
|
171
|
+
if (fs.existsSync(stateFile)) {
|
|
172
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
173
|
+
for (const [id, settings] of Object.entries(state.buildLocations)) {
|
|
174
|
+
if (objects[id]) Object.assign(objects[id].buildSettings ||= {}, settings);
|
|
175
|
+
}
|
|
176
|
+
fs.unlinkSync(stateFile);
|
|
177
|
+
fs.rmSync(path.join(path.dirname(projectFile), 'cocos-sdk-spm.cjs'), { force: true });
|
|
178
|
+
}
|
|
179
|
+
writeProject(projectFile, project);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = { installSwiftPackage, removeSwiftPackages, readProject, applicationTarget };
|
|
183
|
+
|
|
184
|
+
if (require.main === module && process.argv[2] === '--restore') {
|
|
185
|
+
const directory = path.dirname(__filename);
|
|
186
|
+
const state = JSON.parse(fs.readFileSync(path.join(directory, 'cocos-sdk-spm.json'), 'utf8'));
|
|
187
|
+
for (const item of state.packages) {
|
|
188
|
+
installSwiftPackage(path.join(directory, 'project.pbxproj'), path.resolve(directory, item.packageDirectory), item.productName, item.targetName);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
#import "FTCocosBridge.h"
|
|
2
2
|
#import "FTCocosReplayImageJobs.h"
|
|
3
3
|
|
|
4
|
-
#import "
|
|
4
|
+
#import "FTMobileAgent.h"
|
|
5
|
+
#import "FTSDKConfig.h"
|
|
6
|
+
#import "FTRumConfig.h"
|
|
7
|
+
#import "FTLoggerConfig.h"
|
|
8
|
+
#import "FTMobileConfig.h"
|
|
9
|
+
#import "FTExternalDataManager.h"
|
|
10
|
+
#import "FTResourceMetricsModel.h"
|
|
11
|
+
#import "FTResourceContentModel.h"
|
|
5
12
|
#import "FTSessionReplay.h"
|
|
6
13
|
#import <UIKit/UIKit.h>
|
|
7
14
|
#import <objc/message.h>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Pod::Spec.new do |s|
|
|
2
2
|
s.name = 'FTCocosBridge'
|
|
3
|
-
s.version = '0.1.0-alpha.
|
|
3
|
+
s.version = '0.1.0-alpha.5'
|
|
4
4
|
s.summary = 'CloudCare Mobile SDK native bridge for Cocos Creator.'
|
|
5
5
|
s.homepage = 'https://github.com/GuanceCloud/datakit-cocos'
|
|
6
6
|
s.license = { :type => 'Apache-2.0' }
|
|
@@ -13,6 +13,6 @@ Pod::Spec.new do |s|
|
|
|
13
13
|
# Cocos resolves FTCocosBridge by class name at runtime, so no direct symbol
|
|
14
14
|
# reference would otherwise keep the Objective-C category/class in the app.
|
|
15
15
|
s.user_target_xcconfig = { 'OTHER_LDFLAGS' => '$(inherited) -ObjC' }
|
|
16
|
-
s.dependency 'GuanceSDK/Agent', '1.6.8-alpha.
|
|
17
|
-
s.dependency 'GuanceSDK/FTSessionReplay', '1.6.8-alpha.
|
|
16
|
+
s.dependency 'GuanceSDK/Agent', '1.6.8-alpha.5'
|
|
17
|
+
s.dependency 'GuanceSDK/FTSessionReplay', '1.6.8-alpha.5'
|
|
18
18
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// swift-tools-version:5.3
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "FTCocosBridge",
|
|
6
|
+
platforms: [.iOS(.v12)],
|
|
7
|
+
products: [.library(name: "FTCocosBridge", targets: ["FTCocosBridge"])],
|
|
8
|
+
dependencies: [
|
|
9
|
+
.package(url: "https://github.com/GuanceCloud/datakit-ios.git", .exact("1.6.8-alpha.5")),
|
|
10
|
+
],
|
|
11
|
+
targets: [
|
|
12
|
+
.target(
|
|
13
|
+
name: "FTCocosBridge",
|
|
14
|
+
dependencies: [
|
|
15
|
+
.product(name: "GuanceSDK", package: "datakit-ios"),
|
|
16
|
+
.product(name: "GuanceSessionReplay", package: "datakit-ios"),
|
|
17
|
+
],
|
|
18
|
+
path: ".",
|
|
19
|
+
exclude: ["FTCocosBridge.podspec"],
|
|
20
|
+
sources: ["FTCocosBridge.m", "FTCocosReplayImageJobs.m"],
|
|
21
|
+
publicHeadersPath: "include",
|
|
22
|
+
cSettings: [.headerSearchPath(".")],
|
|
23
|
+
linkerSettings: [.linkedFramework("UIKit")]
|
|
24
|
+
),
|
|
25
|
+
]
|
|
26
|
+
)
|