@expo/expo-modules-macros-plugin 0.0.9 → 0.2.0
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/.github/workflows/publish.yml +118 -0
- package/.github/workflows/swift.yml +59 -0
- package/apple/ExpoModulesMacros-tool +0 -0
- package/apple/Package.swift +11 -4
- package/apple/Sources/ExpoModulesMacros/DecorateFunctionBuilder.swift +174 -0
- package/apple/Sources/ExpoModulesMacros/ExpoModuleMacro.swift +170 -19
- package/apple/Sources/ExpoModulesMacros/MacroHelpers.swift +116 -0
- package/apple/Sources/ExpoModulesMacros/RecordMacro.swift +424 -135
- package/apple/Sources/ExpoModulesMacros/SharedObjectMacro.swift +17 -20
- package/apple/build.js +129 -12
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import SwiftSyntaxMacros
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
Member macro applied to a `SharedObject` subclass. Scans the class body for
|
|
7
|
-
declarations marked with `@JS` and synthesizes `
|
|
7
|
+
declarations marked with `@JS` and synthesizes `_synthesizedClassDefinition()`,
|
|
8
8
|
returning a `ClassDefinition` ready to drop into a module's `Class { ... }` slot.
|
|
9
9
|
|
|
10
10
|
@SharedObject
|
|
@@ -79,7 +79,7 @@ public struct SharedObjectMacro: MemberMacro {
|
|
|
79
79
|
: " return Class(\"\(jsName)\", \(typeName).self) {\n\(lines)\n }"
|
|
80
80
|
|
|
81
81
|
let method: DeclSyntax = """
|
|
82
|
-
public static func
|
|
82
|
+
public static func _synthesizedClassDefinition() -> ClassDefinition {
|
|
83
83
|
\(raw: body)
|
|
84
84
|
}
|
|
85
85
|
"""
|
|
@@ -88,28 +88,25 @@ public struct SharedObjectMacro: MemberMacro {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
91
|
+
extension SharedObjectMacro: MemberAttributeMacro {
|
|
92
|
+
public static func expansion(
|
|
93
|
+
of node: AttributeSyntax,
|
|
94
|
+
attachedTo declaration: some DeclGroupSyntax,
|
|
95
|
+
providingAttributesFor member: some DeclSyntaxProtocol,
|
|
96
|
+
in context: some MacroExpansionContext
|
|
97
|
+
) throws -> [AttributeSyntax] {
|
|
98
|
+
guard memberHasJSAttribute(member),
|
|
99
|
+
shouldStampJavaScriptActor(on: member, enclosedBy: declaration) else {
|
|
100
|
+
return []
|
|
100
101
|
}
|
|
102
|
+
return ["@JavaScriptActor"]
|
|
101
103
|
}
|
|
102
|
-
return false
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if let member = type.as(MemberTypeSyntax.self) {
|
|
110
|
-
return member.name.text
|
|
111
|
-
}
|
|
112
|
-
return nil
|
|
106
|
+
// MARK: - Inheritance check
|
|
107
|
+
|
|
108
|
+
private func inheritsFromSharedObject(_ classDecl: ClassDeclSyntax) -> Bool {
|
|
109
|
+
return inheritsFromAny(classDecl, names: ["SharedObject"])
|
|
113
110
|
}
|
|
114
111
|
|
|
115
112
|
// MARK: - Class-scope entry builders
|
package/apple/build.js
CHANGED
|
@@ -1,21 +1,138 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const fs = require('node:fs');
|
|
3
|
+
const childProcess = require('node:child_process');
|
|
4
|
+
const fs = require('node:fs/promises');
|
|
5
|
+
const os = require('node:os');
|
|
5
6
|
const path = require('node:path');
|
|
7
|
+
const { promisify } = require('node:util');
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
execSync('swift build -c release', { stdio: 'inherit', cwd: __dirname });
|
|
9
|
+
const execFile = promisify(childProcess.execFile);
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Runs a command and resolves when it exits successfully.
|
|
13
|
+
*/
|
|
14
|
+
function spawnAsync(command, args, options = {}) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const child = childProcess.spawn(command, args, { stdio: 'inherit', ...options });
|
|
17
|
+
child.once('error', reject);
|
|
18
|
+
child.once('close', (code, signal) => {
|
|
19
|
+
if (code === 0) {
|
|
20
|
+
resolve();
|
|
21
|
+
} else {
|
|
22
|
+
reject(new Error(`\`${command} ${args.join(' ')}\` exited with ${signal ?? `code ${code}`}`));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Builds the macro plugin for the given architecture and returns the path to the built binary.
|
|
30
|
+
* SwiftPM always builds macro tools for the host architecture, so the x86_64 slice
|
|
31
|
+
* is built by running the whole toolchain under Rosetta with `arch -${arch}`.
|
|
32
|
+
*/
|
|
33
|
+
async function buildForArch(arch) {
|
|
34
|
+
const buildArgs = ['build', '-c', 'release'];
|
|
35
|
+
if (arch === os.machine()) {
|
|
36
|
+
await spawnAsync('swift', buildArgs, { cwd: __dirname });
|
|
37
|
+
} else {
|
|
38
|
+
await spawnAsync('arch', [`-${arch}`, 'swift', ...buildArgs], { cwd: __dirname });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const binPath = path.join(__dirname, `.build/${arch}-apple-macosx/release/ExpoModulesMacros-tool`);
|
|
42
|
+
try {
|
|
43
|
+
await fs.access(binPath);
|
|
44
|
+
} catch {
|
|
45
|
+
throw new Error(`Could not find the built ExpoModulesMacros-tool at ${binPath}`);
|
|
46
|
+
}
|
|
47
|
+
return binPath;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Checks whether the Swift toolchain actually runs for the given architecture by
|
|
52
|
+
* verifying the host target triple it reports, e.g. `Target: x86_64-apple-macosx26.0`
|
|
53
|
+
* under Rosetta. Exit status alone is not enough: if `arch` ever fell back to the
|
|
54
|
+
* native architecture, the probe would pass but the build would produce a wrong slice.
|
|
55
|
+
*/
|
|
56
|
+
async function canRunSwiftForArch(arch) {
|
|
57
|
+
try {
|
|
58
|
+
const { stdout } = await execFile('arch', [`-${arch}`, 'swift', '--version']);
|
|
59
|
+
return stdout.includes(`${arch}-apple`);
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Checks whether the toolchain can build for the given architecture.
|
|
67
|
+
* Building x86_64 on Apple Silicon requires Rosetta, so when it is missing,
|
|
68
|
+
* try to install it before giving up.
|
|
69
|
+
*/
|
|
70
|
+
async function canBuildForArch(arch) {
|
|
71
|
+
if (arch === os.machine()) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (await canRunSwiftForArch(arch)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (arch === 'x86_64' && os.machine() === 'arm64') {
|
|
78
|
+
try {
|
|
79
|
+
console.log('Installing Rosetta to build the x86_64 slice...');
|
|
80
|
+
await spawnAsync('sudo', ['softwareupdate', '--install-rosetta', '--agree-to-license']);
|
|
81
|
+
return await canRunSwiftForArch(arch);
|
|
82
|
+
} catch {
|
|
83
|
+
// No sudo access or the install failed - fall through to the unsupported arch warning.
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
11
88
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Asserts that the built binary contains a slice for each of the given architectures.
|
|
91
|
+
*/
|
|
92
|
+
async function verifyArchs(binaryPath, archs) {
|
|
93
|
+
const { stdout } = await execFile('lipo', ['-archs', binaryPath]);
|
|
94
|
+
const builtArchs = stdout.trim().split(/\s+/);
|
|
95
|
+
for (const arch of archs) {
|
|
96
|
+
if (!builtArchs.includes(arch)) {
|
|
97
|
+
throw new Error(`The built binary at ${binaryPath} is missing the ${arch} slice`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function main() {
|
|
103
|
+
const outputPath = path.join(__dirname, 'ExpoModulesMacros-tool');
|
|
104
|
+
|
|
105
|
+
const archs = [];
|
|
106
|
+
for (const arch of ['arm64', 'x86_64']) {
|
|
107
|
+
if (await canBuildForArch(arch)) {
|
|
108
|
+
archs.push(arch);
|
|
109
|
+
} else {
|
|
110
|
+
console.warn(`The Swift toolchain cannot run for ${arch} - the built binary will not support ${arch} Macs.`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (archs.length === 0) {
|
|
114
|
+
throw new Error('The Swift toolchain is not available for any supported architecture');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Builds run sequentially as SwiftPM locks the shared .build directory.
|
|
118
|
+
const binPaths = [];
|
|
119
|
+
for (const arch of archs) {
|
|
120
|
+
binPaths.push(await buildForArch(arch));
|
|
121
|
+
}
|
|
16
122
|
|
|
17
|
-
|
|
18
|
-
|
|
123
|
+
await fs.rm(outputPath, { force: true });
|
|
124
|
+
if (binPaths.length > 1) {
|
|
125
|
+
await spawnAsync('lipo', ['-create', ...binPaths, '-output', outputPath]);
|
|
126
|
+
} else {
|
|
127
|
+
await fs.copyFile(binPaths[0], outputPath);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await spawnAsync('strip', [outputPath]);
|
|
131
|
+
await verifyArchs(outputPath, archs);
|
|
132
|
+
await spawnAsync('lipo', ['-info', outputPath]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
main().catch((error) => {
|
|
19
136
|
console.error('Build failed:', error.message);
|
|
20
137
|
process.exit(1);
|
|
21
|
-
}
|
|
138
|
+
});
|