Skip to content

Commit 185e9db

Browse files
committed
feat(node): implement os built-in module, part 2
- feat: implement most `os` methods - fix: type hint failures - fix: stubbed os - fix: access to os intrinsic at facade layer - fix: initial exception resolving process api Signed-off-by: Sam Gammon <[email protected]>
1 parent 0e50360 commit 185e9db

File tree

4 files changed

+116
-32
lines changed

4 files changed

+116
-32
lines changed

packages/graalvm/src/main/kotlin/elide/runtime/feature/js/node/NodeJsFeature.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,10 @@ package elide.runtime.feature.js.node
1515
import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess
1616
import elide.annotations.internal.VMFeature
1717
import elide.runtime.feature.FrameworkFeature
18+
import elide.runtime.gvm.internals.node.os.NodeOperatingSystem
1819
import elide.runtime.gvm.internals.node.path.NodePaths
1920
import elide.runtime.gvm.internals.node.process.NodeProcess
20-
import elide.runtime.intrinsics.js.node.FilesystemAPI
21-
import elide.runtime.intrinsics.js.node.FilesystemPromiseAPI
22-
import elide.runtime.intrinsics.js.node.PathAPI
23-
import elide.runtime.intrinsics.js.node.ProcessAPI
21+
import elide.runtime.intrinsics.js.node.*
2422

2523
/** GraalVM feature which enables reflective access to built-in Node modules. */
2624
@VMFeature internal class NodeJsFeature : FrameworkFeature {
@@ -37,6 +35,12 @@ import elide.runtime.intrinsics.js.node.ProcessAPI
3735
registerClassForReflection(access, NodePaths.PosixPaths::class.java.name)
3836
registerClassForReflection(access, NodePaths.WindowsPaths::class.java.name)
3937

38+
// `os`
39+
registerClassForReflection(access, OperatingSystemAPI::class.java.name)
40+
registerClassForReflection(access, NodeOperatingSystem.BaseOS::class.java.name)
41+
registerClassForReflection(access, NodeOperatingSystem.Posix::class.java.name)
42+
registerClassForReflection(access, NodeOperatingSystem.Win32::class.java.name)
43+
4044
// `fs`
4145
registerClassForReflection(access, FilesystemAPI::class.java.name)
4246
registerClassForReflection(access, FilesystemPromiseAPI::class.java.name)

packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/node/os/NodeOperatingSystem.kt

Lines changed: 104 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,16 @@
1515

1616
package elide.runtime.gvm.internals.node.os
1717

18+
import io.micronaut.core.annotation.Introspected
19+
import io.micronaut.core.annotation.ReflectiveAccess
20+
import org.graalvm.polyglot.Value
21+
import org.graalvm.polyglot.proxy.ProxyExecutable
22+
import org.graalvm.polyglot.proxy.ProxyObject
23+
import org.jetbrains.annotations.VisibleForTesting
1824
import oshi.SystemInfo
1925
import java.lang.management.ManagementFactory
2026
import java.lang.management.OperatingSystemMXBean
27+
import java.net.InetAddress
2128
import java.nio.ByteOrder
2229
import elide.annotations.Factory
2330
import elide.annotations.Singleton
@@ -142,6 +149,32 @@ private val STUBBED_USER: UserInfo = UserInfo.of(
142149
STUBBED_USERINFO_GID,
143150
)
144151

152+
// Module member names.
153+
private val moduleMembers = arrayOf(
154+
"EOL",
155+
"devNull",
156+
"constants",
157+
"availableParallelism",
158+
"arch",
159+
"cpus",
160+
"endianness",
161+
"freemem",
162+
"getPriority",
163+
"homedir",
164+
"hostname",
165+
"loadavg",
166+
"networkInterfaces",
167+
"platform",
168+
"release",
169+
"setPriority",
170+
"tmpdir",
171+
"totalmem",
172+
"type",
173+
"uptime",
174+
"userInfo",
175+
"version",
176+
)
177+
145178
// Installs the Node OS module into the intrinsic bindings.
146179
@Intrinsic @Factory internal class NodeOperatingSystemModule : AbstractNodeBuiltinModule() {
147180
// Provide a compliant instance of the OS API to the DI context.
@@ -159,13 +192,47 @@ internal object NodeOperatingSystem {
159192
/** Primordial symbol where the OS API implementation is installed. */
160193
internal const val SYMBOL: String = "__Elide_node_os__"
161194

195+
internal abstract class ModuleBase : ProxyObject, OperatingSystemAPI {
196+
override fun getMemberKeys(): Array<String> = moduleMembers
197+
override fun hasMember(key: String): Boolean = key in moduleMembers
198+
override fun getMember(key: String?): Any? = when (key) {
199+
"EOL" -> EOL
200+
"devNull" -> devNull
201+
"constants" -> constants
202+
"availableParallelism" -> ProxyExecutable { availableParallelism() }
203+
"arch" -> ProxyExecutable { arch() }
204+
"cpus" -> ProxyExecutable { cpus() }
205+
"endianness" -> ProxyExecutable { endianness() }
206+
"freemem" -> ProxyExecutable { freemem() }
207+
"getPriority" -> ProxyExecutable { args -> getPriority(args.getOrNull(0)) }
208+
"homedir" -> ProxyExecutable { homedir() }
209+
"hostname" -> ProxyExecutable { hostname() }
210+
"loadavg" -> ProxyExecutable { loadavg() }
211+
"networkInterfaces" -> ProxyExecutable { networkInterfaces() }
212+
"platform" -> ProxyExecutable { platform() }
213+
"release" -> ProxyExecutable { release() }
214+
"setPriority" -> ProxyExecutable { args -> setPriority(args.getOrNull(0), args.getOrNull(1)) }
215+
"tmpdir" -> ProxyExecutable { tmpdir() }
216+
"totalmem" -> ProxyExecutable { totalmem() }
217+
"type" -> ProxyExecutable { type() }
218+
"uptime" -> ProxyExecutable { uptime() }
219+
"userInfo" -> ProxyExecutable { args -> userInfo(args.getOrNull(0)?.`as`(UserInfoOptions::class.java)) }
220+
"version" -> ProxyExecutable { version() }
221+
else -> null
222+
}
223+
224+
override fun putMember(key: String?, value: Value?) {
225+
throw UnsupportedOperationException("Cannot modify `os` module")
226+
}
227+
}
228+
162229
/**
163230
* ## Stubbed OS
164231
*
165232
* Stubbed implementation of the `os` module for use in simulated or host-restricted environments; returns static,
166233
* predetermined values for all OS-related calls. Where necessary, chooses POSIX-style values.
167234
*/
168-
internal object StubbedOs : OperatingSystemAPI {
235+
internal class StubbedOs : ModuleBase(), OperatingSystemAPI {
169236
override val family: OSType get() = POSIX
170237
override val EOL: String get() = posixEOL
171238
override val constants: OperatingSystemConstants get() = TODO("Not yet implemented: `os.constants`")
@@ -175,19 +242,19 @@ internal object NodeOperatingSystem {
175242
override fun cpus(): List<CPUInfo> = STUBBED_CPUS
176243
override fun endianness(): String = STUBBED_ENDIANNESS
177244
override fun freemem(): Long = STUBBED_FREEMEM
178-
override fun getPriority(pid: Long?): Int = STUBBED_PRIORITY
245+
override fun getPriority(pid: Value?): Int = STUBBED_PRIORITY
179246
override fun homedir(): String = STUBBED_HOMEDIR
180247
override fun hostname(): String = STUBBED_HOSTNAME
181248
override fun loadavg(): List<Double> = listOf(0.0, 0.0, 0.0)
182249
override fun networkInterfaces(): Map<String, List<NetworkInterfaceInfo>> = STUBBED_NICS
183250
override fun platform(): String = STUBBED_PLATFORM
184251
override fun release(): String = STUBBED_RELEASE
185-
override fun setPriority(pid: Long, priority: Int) = Unit
252+
override fun setPriority(pid: Value?, priority: Value?) = Unit
186253
override fun tmpdir(): String = STUBBED_TMPDIR
187254
override fun totalmem(): Long = STUBBED_TOTALMEM
188255
override fun type(): String = STUBBED_TYPE
189256
override fun uptime(): Double = STUBBED_UPTIME
190-
override fun userInfo(options: UserInfoOptions): UserInfo = STUBBED_USER
257+
override fun userInfo(options: UserInfoOptions?): UserInfo = STUBBED_USER
191258
override fun version(): String = STUBBED_VERSION
192259
}
193260

@@ -200,12 +267,22 @@ internal object NodeOperatingSystem {
200267
* @see Win32 for Win32-style systems
201268
* @see Posix for POSIX-style systems
202269
*/
203-
abstract class BaseOS protected constructor (override val family: OSType) : OperatingSystemAPI {
270+
@ReflectiveAccess @Introspected abstract class BaseOS protected constructor (override val family: OSType) :
271+
ModuleBase(),
272+
OperatingSystemAPI,
273+
ProxyObject {
204274
/** Obtain system info. */
205275
private val systemInfo: SystemInfo by lazy { SystemInfo() }
206276
private val osManager: OperatingSystemMXBean by lazy { ManagementFactory.getOperatingSystemMXBean() }
207277

208-
private fun mapJvmArchToNodeArch(arch: String): String = when (arch.lowercase().trim()) {
278+
private fun wrapCleanup(subject: String?): String =
279+
subject?.trim()?.replace(Regex("[\n\r]"), "") ?: ""
280+
281+
private fun trimTrailing(subject: String?): String =
282+
subject?.endsWith("/")?.let { if (it) subject.dropLast(1) else subject } ?: ""
283+
284+
@VisibleForTesting
285+
internal fun mapJvmArchToNodeArch(arch: String): String = when (arch.lowercase().trim()) {
209286
JVM_X86 -> NODE_X86
210287
JVM_X86_64 -> NODE_X64
211288
JVM_AMD64 -> NODE_X64
@@ -214,9 +291,10 @@ internal object NodeOperatingSystem {
214291
else -> "unknown"
215292
}
216293

217-
private fun mapJvmOsToNodeOs(os: String = System.getProperty("os.name")): String = when (os.lowercase().trim()) {
294+
@VisibleForTesting
295+
internal fun mapJvmOsToNodeOs(os: String = System.getProperty("os.name")): String = when (os.lowercase().trim()) {
218296
"aix" -> OS_AIX
219-
"darwin", "macos" -> OS_DARWIN
297+
"mac os x" -> OS_DARWIN
220298
"freebsd" -> OS_FREEBSD
221299
"linux" -> OS_LINUX
222300
"openbsd" -> OS_OPENBSD
@@ -225,8 +303,12 @@ internal object NodeOperatingSystem {
225303
else -> "unknown"
226304
}
227305

228-
private fun wrapCleanup(subject: String?): String {
229-
return subject?.trim()?.replace(Regex("[\n\r]"), "") ?: ""
306+
@VisibleForTesting
307+
internal fun typeForOsName(osName: String?): String = when (osName?.lowercase()?.trim()) {
308+
"linux" -> OS_TYPE_LINUX
309+
"mac os x" -> OS_TYPE_DARWIN
310+
"windows" -> OS_TYPE_WINDOWS
311+
else -> "unknown"
230312
}
231313

232314
@Polyglot override fun availableParallelism(): Int = systemInfo.hardware.processor.logicalProcessorCount
@@ -252,10 +334,10 @@ internal object NodeOperatingSystem {
252334
@Polyglot override fun freemem(): Long = Runtime.getRuntime().freeMemory()
253335

254336
// @TODO(sgammon): not yet implemented
255-
@Polyglot override fun getPriority(pid: Long?): Int = PRIORITY_NORMAL
337+
@Polyglot override fun getPriority(pid: Value?): Int = PRIORITY_NORMAL
256338

257339
@Polyglot override fun homedir(): String = wrapCleanup(System.getProperty("user.home"))
258-
@Polyglot override fun hostname(): String = wrapCleanup(System.getProperty("os.hostname"))
340+
@Polyglot override fun hostname(): String = wrapCleanup(InetAddress.getLocalHost().hostName)
259341

260342
@Polyglot override fun loadavg(): List<Double> = osManager.systemLoadAverage.let {
261343
listOf(it, 0.0, 0.0) // @TODO: implement 5 and 15 minute averages
@@ -316,32 +398,26 @@ internal object NodeOperatingSystem {
316398
}
317399

318400
@Polyglot override fun platform(): String = mapJvmOsToNodeOs()
319-
320401
@Polyglot override fun release(): String = systemInfo.operatingSystem.toString()
321402

322403
// @TODO(sgammon): not yet implemented
323-
@Polyglot override fun setPriority(pid: Long, priority: Int) = Unit
324-
@Polyglot override fun tmpdir(): String = wrapCleanup(System.getProperty("java.io.tmpdir"))
404+
@Polyglot override fun setPriority(pid: Value?, priority: Value?) = Unit
405+
@Polyglot override fun tmpdir(): String = trimTrailing(wrapCleanup(System.getProperty("java.io.tmpdir")))
325406
@Polyglot override fun totalmem(): Long = Runtime.getRuntime().totalMemory()
326407

327-
@Polyglot override fun type(): String = when (System.getProperty("os.name")) {
328-
"linux" -> OS_TYPE_LINUX
329-
"darwin" -> OS_TYPE_DARWIN
330-
"windows" -> OS_TYPE_WINDOWS
331-
else -> "unknown"
332-
}
408+
@Polyglot override fun type(): String = typeForOsName(System.getProperty("os.name"))
333409

334410
@Polyglot override fun uptime(): Double = systemInfo.operatingSystem.systemUptime.toDouble()
335411

336-
@Polyglot override fun userInfo(options: UserInfoOptions): UserInfo {
412+
@Polyglot override fun userInfo(options: UserInfoOptions?): UserInfo {
337413
TODO("Not yet implemented: `os.userInfo`")
338414
}
339415

340416
@Polyglot override fun version(): String = systemInfo.operatingSystem.versionInfo.version
341417
}
342418

343419
// Implements Operating System API calls for Win32-style systems.
344-
object Win32 : BaseOS(WIN32), OperatingSystemAPI {
420+
@ReflectiveAccess @Introspected object Win32 : BaseOS(WIN32), OperatingSystemAPI {
345421
@get:Polyglot override val EOL: String get() = win32EOL
346422
@get:Polyglot override val devNull: String get() = win32DevNull
347423

@@ -350,22 +426,25 @@ internal object NodeOperatingSystem {
350426
}
351427

352428
// Implements Operating System API calls for POSIX-style systems.
353-
object Posix : BaseOS(POSIX), OperatingSystemAPI {
429+
@ReflectiveAccess @Introspected object Posix : BaseOS(POSIX), OperatingSystemAPI {
354430
@get:Polyglot override val EOL: String get() = posixEOL
355431
@get:Polyglot override val devNull: String get() = posixDevNull
356432

357433
override val constants: OperatingSystemConstants
358434
get() = TODO("Not yet implemented: `os.constants` (POSIX)")
359435
}
360436

437+
// Stubbed OS module API singleton.
438+
private val stubbed: StubbedOs by lazy { StubbedOs() }
439+
361440
/**
362441
* ## Node Operating System API: Stubbed
363442
*
364443
* Creates a stubbed instance of the [OperatingSystemAPI] for use in simulated or host-restricted environments.
365444
*
366445
* @return A stubbed instance of the [OperatingSystemAPI].
367446
*/
368-
@JvmStatic fun stubbed(): OperatingSystemAPI = StubbedOs
447+
@JvmStatic fun stubbed(): OperatingSystemAPI = stubbed
369448

370449
/**
371450
* ## Node Operating System API: Create or Obtain

packages/graalvm/src/main/kotlin/elide/runtime/intrinsics/js/node/OperatingSystemAPI.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package elide.runtime.intrinsics.js.node
1717

18+
import org.graalvm.polyglot.Value
1819
import elide.annotations.API
1920
import elide.runtime.intrinsics.js.node.os.*
2021
import elide.vm.annotations.Polyglot
@@ -115,7 +116,7 @@ import elide.vm.annotations.Polyglot
115116
* @param pid The process ID of the process to query.
116117
* @return The scheduling priority of the process.
117118
*/
118-
@Polyglot public fun getPriority(pid: Long? = null): Int
119+
@Polyglot public fun getPriority(pid: Value? = null): Int
119120

120121
/**
121122
* ## Method: `os.homedir()`
@@ -179,7 +180,7 @@ import elide.vm.annotations.Polyglot
179180
* @param pid The process ID of the process to modify; defaults to `0`.
180181
* @param priority The new scheduling priority for the process.
181182
*/
182-
@Polyglot public fun setPriority(pid: Long = 0, priority: Int = PRIORITY_NORMAL)
183+
@Polyglot public fun setPriority(pid: Value? = Value.asValue(0), priority: Value? = Value.asValue(PRIORITY_NORMAL))
183184

184185
/**
185186
* ## Method: `os.tmpdir()`
@@ -224,7 +225,7 @@ import elide.vm.annotations.Polyglot
224225
*
225226
* @param options Options for the user information query.
226227
*/
227-
@Polyglot public fun userInfo(options: UserInfoOptions): UserInfo
228+
@Polyglot public fun userInfo(options: UserInfoOptions? = null): UserInfo?
228229

229230
/**
230231
* ## Method: `os.version()`

0 commit comments

Comments
 (0)