@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.
@@ -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 `_exposedClassDefinition()`,
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 _exposedClassDefinition() -> ClassDefinition {
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
- // MARK: - Inheritance check
92
-
93
- private func inheritsFromSharedObject(_ classDecl: ClassDeclSyntax) -> Bool {
94
- guard let inherited = classDecl.inheritanceClause?.inheritedTypes else {
95
- return false
96
- }
97
- for entry in inherited {
98
- if baseIdentifier(of: entry.type) == "SharedObject" {
99
- return true
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
- private func baseIdentifier(of type: TypeSyntax) -> String? {
106
- if let identifier = type.as(IdentifierTypeSyntax.self) {
107
- return identifier.name.text
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 { execSync } = require('node:child_process');
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
- try {
8
- execSync('swift build -c release', { stdio: 'inherit', cwd: __dirname });
9
+ const execFile = promisify(childProcess.execFile);
9
10
 
10
- fs.rmSync(path.join(__dirname, 'ExpoModulesMacros-tool'), { force: true });
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
- fs.copyFileSync(
13
- path.join(__dirname, '.build/arm64-apple-macosx/release/ExpoModulesMacros-tool'),
14
- path.join(__dirname, 'ExpoModulesMacros-tool')
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
- execSync('strip ExpoModulesMacros-tool', { stdio: 'inherit', cwd: __dirname });
18
- } catch (error) {
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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/expo-modules-macros-plugin",
3
- "version": "0.0.9",
3
+ "version": "0.2.0",
4
4
  "description": "Swift macro plugin for Expo modules",
5
5
  "license": "MIT",
6
6
  "author": "650 Industries, Inc.",