@agoric/xsnap 0.14.3-u19.1 → 0.14.3-u20.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.
Files changed (3) hide show
  1. package/api.js +1 -1
  2. package/package.json +16 -15
  3. package/src/build.js +182 -191
package/api.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * Also, update golden master test/xs-perf.test.js to reflect new meter
8
8
  * version.
9
9
  */
10
- export const METER_TYPE = 'xs-meter-34';
10
+ export const METER_TYPE = 'xs-meter-35';
11
11
 
12
12
  export const ExitCode = {
13
13
  E_UNKNOWN_ERROR: -1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/xsnap",
3
- "version": "0.14.3-u19.1",
3
+ "version": "0.14.3-u20.0",
4
4
  "description": "Snapshotting VM worker based on Moddable's XS Javascript engine",
5
5
  "author": "Agoric",
6
6
  "license": "Apache-2.0",
@@ -28,25 +28,26 @@
28
28
  "test:xs": "exit 0"
29
29
  },
30
30
  "dependencies": {
31
- "@agoric/internal": "^0.4.0-u19.1",
32
- "@agoric/xsnap-lockdown": "^0.14.1-u19.0",
33
- "@endo/bundle-source": "^3.5.1",
34
- "@endo/errors": "^1.2.9",
35
- "@endo/eventual-send": "^1.3.0",
36
- "@endo/init": "^1.1.8",
37
- "@endo/netstring": "^1.0.14",
38
- "@endo/promise-kit": "^1.1.9",
39
- "@endo/stream": "^1.2.9",
40
- "@endo/stream-node": "^1.1.9",
31
+ "@agoric/internal": "^0.4.0-u20.0",
32
+ "@agoric/xsnap-lockdown": "^0.14.1-u20.0",
33
+ "@endo/bundle-source": "^4.0.0",
34
+ "@endo/errors": "^1.2.10",
35
+ "@endo/eventual-send": "^1.3.1",
36
+ "@endo/init": "^1.1.9",
37
+ "@endo/netstring": "^1.0.15",
38
+ "@endo/promise-kit": "^1.1.10",
39
+ "@endo/stream": "^1.2.10",
40
+ "@endo/stream-node": "^1.1.10",
41
41
  "glob": "^7.1.6",
42
42
  "tmp": "^0.2.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@endo/base64": "^1.0.9",
46
- "@endo/nat": "^5.0.14",
46
+ "@endo/nat": "^5.1.0",
47
47
  "@types/glob": "^8.1.0",
48
48
  "ava": "^5.3.0",
49
- "c8": "^10.1.2"
49
+ "c8": "^10.1.2",
50
+ "execa": "^7.2.0"
50
51
  },
51
52
  "files": [
52
53
  "LICENSE*",
@@ -76,7 +77,7 @@
76
77
  "workerThreads": false
77
78
  },
78
79
  "typeCoverage": {
79
- "atLeast": 93.51
80
+ "atLeast": 93.56
80
81
  },
81
- "gitHead": "a04d2bf43a9753d123954b52c6ba8d35083a3c8f"
82
+ "gitHead": "8e4207fa19dabf76c1f91f8779b5b5b93570ecea"
82
83
  }
package/src/build.js CHANGED
@@ -10,9 +10,18 @@ const { freeze } = Object;
10
10
  /** @param {string} path */
11
11
  const asset = path => fileURLToPath(new URL(path, import.meta.url));
12
12
 
13
+ /** @param {Promise<unknown>} p */
14
+ const isRejected = p =>
15
+ p.then(
16
+ () => false,
17
+ () => true,
18
+ );
19
+
20
+ /** @typedef {{ path: string, make?: string }} ModdablePlatform */
21
+
13
22
  const ModdableSDK = {
14
23
  MODDABLE: asset('../moddable'),
15
- /** @type { Record<string, { path: string, make?: string }>} */
24
+ /** @type { Record<string, ModdablePlatform>} */
16
25
  platforms: {
17
26
  Linux: { path: 'lin' },
18
27
  Darwin: { path: 'mac' },
@@ -22,7 +31,8 @@ const ModdableSDK = {
22
31
  };
23
32
 
24
33
  /**
25
- * Adapt spawn to Promises style.
34
+ * Create promise-returning functions for asynchronous execution of no-input
35
+ * commands.
26
36
  *
27
37
  * @param {string} command
28
38
  * @param {{
@@ -48,6 +58,8 @@ function makeCLI(command, { spawn }) {
48
58
 
49
59
  return freeze({
50
60
  /**
61
+ * Run the command, writing directly to stdout and stderr.
62
+ *
51
63
  * @param {string[]} args
52
64
  * @param {{ cwd?: string }} [opts]
53
65
  */
@@ -55,26 +67,30 @@ function makeCLI(command, { spawn }) {
55
67
  const { cwd = '.' } = opts || {};
56
68
  const child = spawn(command, args, {
57
69
  cwd,
58
- stdio: ['inherit', 'inherit', 'inherit'],
70
+ stdio: ['ignore', 'inherit', 'inherit'],
59
71
  });
60
72
  return wait(child);
61
73
  },
62
74
  /**
75
+ * Run the command, writing directly to stderr but capturing and returning
76
+ * stdout.
77
+ *
63
78
  * @param {string[]} args
64
- * @param {{ cwd?: string }} [opts]
79
+ * @param {{ cwd?: string, fullOutput?: boolean }} [opts]
80
+ * @returns {Promise<string>} command output, stripped of trailing
81
+ * whitespace unless option "fullOutput" is true
65
82
  */
66
- pipe: (args, opts) => {
67
- const { cwd = '.' } = opts || {};
83
+ pipe: async (args, opts) => {
84
+ const { cwd = '.', fullOutput = false } = opts || {};
68
85
  const child = spawn(command, args, {
69
86
  cwd,
70
- stdio: ['inherit', 'pipe', 'inherit'],
71
- });
72
- let output = '';
73
- child.stdout.setEncoding('utf8');
74
- child.stdout.on('data', data => {
75
- output += data.toString();
87
+ stdio: ['ignore', 'pipe', 'inherit'],
76
88
  });
77
- return wait(child).then(() => output);
89
+ const chunks = [];
90
+ child.stdout.on('data', chunk => chunks.push(chunk));
91
+ await wait(child);
92
+ const output = Buffer.concat(chunks).toString('utf8');
93
+ return fullOutput ? output : output.trimEnd();
78
94
  },
79
95
  });
80
96
  }
@@ -85,28 +101,6 @@ function makeCLI(command, { spawn }) {
85
101
  * @param {{ git: ReturnType<typeof makeCLI> }} io
86
102
  */
87
103
  const makeSubmodule = (path, repoUrl, { git }) => {
88
- /** @param {string} text */
89
- const parseStatus = text =>
90
- text
91
- .split('\n')
92
- // From `git submodule --help`:
93
- // Show the status of the submodules. This will print the SHA-1 of the
94
- // currently checked out commit for each submodule, along with the
95
- // submodule path and the output of git describe for the SHA-1. Each
96
- // SHA-1 will possibly be prefixed with - if the submodule is not
97
- // initialized, + if the currently checked out submodule commit does
98
- // not match the SHA-1 found in the index of the containing repository
99
- // and U if the submodule has merge conflicts.
100
- //
101
- // We discovered that in other cases, the prefix is a single space.
102
- .map(line => [line[0], ...line.slice(1).split(' ', 3)])
103
- .map(([prefix, hash, statusPath, describe]) => ({
104
- prefix,
105
- hash,
106
- path: statusPath,
107
- describe,
108
- }));
109
-
110
104
  return freeze({
111
105
  path,
112
106
  clone: async () => git.run(['clone', repoUrl, path]),
@@ -114,90 +108,107 @@ const makeSubmodule = (path, repoUrl, { git }) => {
114
108
  checkout: async commitHash =>
115
109
  git.run(['checkout', commitHash], { cwd: path }),
116
110
  init: async () => git.run(['submodule', 'update', '--init', '--checkout']),
117
- status: async () =>
118
- git.pipe(['submodule', 'status', path]).then(parseStatus),
119
- /** @param {string} leaf */
111
+ status: async () => {
112
+ const line = await git.pipe(['submodule', 'status', path]);
113
+ // From `git submodule --help`:
114
+ // status [--cached] [--recursive] [--] [<path>...]
115
+ // Show the status of the submodules. This will print the SHA-1 of the
116
+ // currently checked out commit for each submodule, along with the
117
+ // submodule path and the output of git describe for the SHA-1. Each
118
+ // SHA-1 will possibly be prefixed with - if the submodule is not
119
+ // initialized, + if the currently checked out submodule commit does
120
+ // not match the SHA-1 found in the index of the containing repository
121
+ // and U if the submodule has merge conflicts.
122
+ //
123
+ // We discovered that in other cases, the prefix is a single space.
124
+ const prefix = line[0];
125
+ const [hash, statusPath, ...describe] = line.slice(1).split(' ');
126
+ return {
127
+ prefix,
128
+ hash,
129
+ path: statusPath,
130
+ describe: describe.join(' '),
131
+ };
132
+ },
133
+ /**
134
+ * Read a specific configuration value for this submodule (e.g., "path" or
135
+ * "url") from the top-level .gitmodules.
136
+ *
137
+ * @param {string} leaf
138
+ */
120
139
  config: async leaf => {
121
140
  // git rev-parse --show-toplevel
122
- const top = await git
123
- .pipe(['rev-parse', '--show-toplevel'])
124
- .then(l => l.trimEnd());
125
- // assume full paths
126
- const name = path.slice(top.length + 1);
127
- // git config -f ../../.gitmodules --get submodule."$name".url
128
- const value = await git
129
- .pipe([
130
- 'config',
131
- '-f',
132
- `${top}/.gitmodules`,
133
- '--get',
134
- `submodule.${name}.${leaf}`,
135
- ])
136
- .then(l => l.trimEnd());
141
+ const repoRoot = await git.pipe(['rev-parse', '--show-toplevel']);
142
+ if (!path.startsWith(`${repoRoot}/`)) {
143
+ throw Error(
144
+ `Expected submodule path ${path} to be a subdirectory of repository ${repoRoot}`,
145
+ );
146
+ }
147
+ const relativePath = path.slice(repoRoot.length + 1);
148
+ // git config -f ../../.gitmodules --get submodule.${relativePath}.${leaf}
149
+ const value = await git.pipe([
150
+ 'config',
151
+ '-f',
152
+ `${repoRoot}/.gitmodules`,
153
+ '--get',
154
+ `submodule.${relativePath}.${leaf}`,
155
+ ]);
137
156
  return value;
138
157
  },
139
158
  });
140
159
  };
141
160
 
142
161
  /**
143
- * @param {boolean} showEnv
162
+ * @typedef {{
163
+ * url: string,
164
+ * path: string,
165
+ * commitHash?: string,
166
+ * envPrefix: string,
167
+ * }} SubmoduleDescriptor
168
+ */
169
+
170
+ /**
171
+ * @param {SubmoduleDescriptor[]} submodules
144
172
  * @param {{
145
- * env: Record<string, string | undefined>,
173
+ * git: ReturnType<typeof makeCLI>,
146
174
  * stdout: typeof process.stdout,
147
- * spawn: typeof import('child_process').spawn,
148
- * fs: {
149
- * existsSync: typeof import('fs').existsSync,
150
- * rmdirSync: typeof import('fs').rmdirSync,
151
- * readFile: typeof import('fs').promises.readFile,
152
- * },
153
175
  * }} io
154
176
  */
155
- const updateSubmodules = async (showEnv, { env, stdout, spawn, fs }) => {
156
- const git = makeCLI('git', { spawn });
157
-
158
- // When changing/adding entries here, make sure to search the whole project
159
- // for `@@AGORIC_DOCKER_SUBMODULES@@`
160
- const submodules = [
161
- {
162
- url: env.MODDABLE_URL || 'https://github.com/agoric-labs/moddable.git',
163
- path: ModdableSDK.MODDABLE,
164
- commitHash: env.MODDABLE_COMMIT_HASH,
165
- envPrefix: 'MODDABLE_',
166
- },
167
- {
168
- url:
169
- env.XSNAP_NATIVE_URL || 'https://github.com/agoric-labs/xsnap-pub.git',
170
- path: asset('../xsnap-native'),
171
- commitHash: env.XSNAP_NATIVE_COMMIT_HASH,
172
- envPrefix: 'XSNAP_NATIVE_',
173
- },
174
- ];
175
-
177
+ const showEnv = async (submodules, { git, stdout }) => {
176
178
  await null;
177
- if (showEnv) {
178
- for (const submodule of submodules) {
179
- const { path, envPrefix, commitHash } = submodule;
180
- if (!commitHash) {
181
- // We need to glean the commitHash and url from Git.
182
- const sm = makeSubmodule(path, '?', { git });
183
- const [[{ hash }], url] = await Promise.all([
184
- sm.status(),
185
- sm.config('url'),
186
- ]);
187
- submodule.commitHash = hash;
188
- submodule.url = url;
189
- }
190
- stdout.write(`${envPrefix}URL=${submodule.url}\n`);
191
- stdout.write(`${envPrefix}COMMIT_HASH=${submodule.commitHash}\n`);
179
+ for (const desc of submodules) {
180
+ const { path, envPrefix } = desc;
181
+ let { url, commitHash } = desc;
182
+ if (!commitHash) {
183
+ // We need to glean the commitHash and url from Git.
184
+ const submodule = makeSubmodule(path, '?', { git });
185
+ const [{ hash }, gitUrl] = await Promise.all([
186
+ submodule.status(),
187
+ submodule.config('url'),
188
+ ]);
189
+ commitHash = hash;
190
+ url = gitUrl;
192
191
  }
193
- return;
192
+ stdout.write(`${envPrefix}URL=${url}\n`);
193
+ stdout.write(`${envPrefix}COMMIT_HASH=${commitHash}\n`);
194
194
  }
195
+ };
195
196
 
197
+ /**
198
+ * @param {SubmoduleDescriptor[]} submodules
199
+ * @param {{
200
+ * fs: Pick<typeof import('fs'), 'existsSync' | 'rmdirSync'>,
201
+ * git: ReturnType<typeof makeCLI>,
202
+ * }} io
203
+ */
204
+ const updateSubmodules = async (submodules, { fs, git }) => {
205
+ await null;
196
206
  for (const { url, path, commitHash } of submodules) {
197
207
  const submodule = makeSubmodule(path, url, { git });
198
208
 
199
- // Allow overriding of the checked-out version of the submodule.
200
- if (commitHash) {
209
+ if (!commitHash) {
210
+ await submodule.init();
211
+ } else {
201
212
  // Do the moral equivalent of submodule update when explicitly overriding.
202
213
  try {
203
214
  fs.rmdirSync(submodule.path);
@@ -208,29 +219,20 @@ const updateSubmodules = async (showEnv, { env, stdout, spawn, fs }) => {
208
219
  await submodule.clone();
209
220
  }
210
221
  await submodule.checkout(commitHash);
211
- } else {
212
- await submodule.init();
213
222
  }
214
223
  }
215
224
  };
216
225
 
217
226
  /**
227
+ * @param {ModdablePlatform} platform
228
+ * @param {boolean} force
218
229
  * @param {{
219
- * spawn: typeof import('child_process').spawn,
220
- * fs: {
221
- * existsSync: typeof import('fs').existsSync,
222
- * rmdirSync: typeof import('fs').rmdirSync,
223
- * readFile: typeof import('fs').promises.readFile,
224
- * writeFile: typeof import('fs').promises.writeFile,
225
- * },
226
- * os: {
227
- * type: typeof import('os').type,
228
- * }
230
+ * fs: Pick<typeof import('fs'), 'existsSync'> &
231
+ * Pick<typeof import('fs').promises, 'readFile' | 'writeFile'>,
232
+ * make: ReturnType<typeof makeCLI>,
229
233
  * }} io
230
- * @param {object} [options]
231
- * @param {boolean} [options.forceBuild]
232
234
  */
233
- const makeXsnap = async ({ spawn, fs, os }, { forceBuild = false } = {}) => {
235
+ const buildXsnap = async (platform, force, { fs, make }) => {
234
236
  const pjson = await fs.readFile(asset('../package.json'), 'utf-8');
235
237
  const pkg = JSON.parse(pjson);
236
238
 
@@ -245,16 +247,10 @@ const makeXsnap = async ({ spawn, fs, os }, { forceBuild = false } = {}) => {
245
247
  : '';
246
248
 
247
249
  const expectedConfigEnvs = configEnvs.concat('').join('\n');
248
- if (forceBuild || existingConfigEnvs.trim() !== expectedConfigEnvs.trim()) {
250
+ if (force || existingConfigEnvs.trim() !== expectedConfigEnvs.trim()) {
249
251
  await fs.writeFile(configEnvFile, expectedConfigEnvs);
250
252
  }
251
253
 
252
- const platform = ModdableSDK.platforms[os.type()];
253
- if (!platform) {
254
- throw Error(`Unsupported OS found: ${os.type()}`);
255
- }
256
-
257
- const make = makeCLI(platform.make || 'make', { spawn });
258
254
  for (const goal of ModdableSDK.buildGoals) {
259
255
  await make.run(
260
256
  [
@@ -280,15 +276,9 @@ const makeXsnap = async ({ spawn, fs, os }, { forceBuild = false } = {}) => {
280
276
  * env: Record<string, string | undefined>,
281
277
  * stdout: typeof process.stdout,
282
278
  * spawn: typeof import('child_process').spawn,
283
- * fs: {
284
- * existsSync: typeof import('fs').existsSync,
285
- * rmdirSync: typeof import('fs').rmdirSync,
286
- * readFile: typeof import('fs').promises.readFile,
287
- * writeFile: typeof import('fs').promises.writeFile,
288
- * },
289
- * os: {
290
- * type: typeof import('os').type,
291
- * }
279
+ * fs: Pick<typeof import('fs'), 'existsSync' | 'rmdirSync'> &
280
+ * Pick<typeof import('fs').promises, 'readFile' | 'writeFile'>,
281
+ * os: Pick<typeof import('os'), 'type'>,
292
282
  * }} io
293
283
  */
294
284
  async function main(args, { env, stdout, spawn, fs, os }) {
@@ -297,76 +287,77 @@ async function main(args, { env, stdout, spawn, fs, os }) {
297
287
  await null;
298
288
 
299
289
  const osType = os.type();
300
- const platform = {
301
- Linux: 'lin',
302
- Darwin: 'mac',
303
- // Windows_NT: 'win', // One can dream.
304
- }[osType];
305
- if (platform === undefined) {
306
- throw Error(`xsnap does not support platform ${osType}`);
290
+ const platform = ModdableSDK.platforms[osType];
291
+ if (!platform) {
292
+ throw Error(`xsnap does not support OS ${osType}`);
307
293
  }
308
294
 
309
- // If this is a working copy of xsnap in a checkout of agoric-sdk, we need to
310
- // either clone or update submodules.
311
- // Otherwise, we are running from an extracted npm tarball and we should not
312
- // attempt to update Git submodules and should make the binary from the
313
- // published source.
314
- //
315
- // These steps will avoid rebuilding native xsnap in the common case for end
316
- // users.
317
- //
318
- // || | X || git
319
- // || X | X || make
320
- // || ---- | ---- || ----
321
- // | bin | src | .git || pack | work ||
322
- // | --- | --- | ---- || ---- | ---- ||
323
- // | | | || | X ||
324
- // | | | X || | ||
325
- // | | X | || X | ||
326
- // | | X | X || | X ||
327
- // | X | | || | ||
328
- // | X | | X || | ||
329
- // | X | X | || X | ||
330
- // | X | X | X || | X ||
331
- //
332
- // We build both release and debug, so checking for one should suffice.
295
+ const git = makeCLI('git', { spawn });
296
+ const make = makeCLI(platform.make || 'make', { spawn });
297
+
298
+ // When changing/adding entries here, make sure to search the whole project
299
+ // for `@@AGORIC_DOCKER_SUBMODULES@@`
300
+ const submodules = [
301
+ {
302
+ url: env.MODDABLE_URL || 'https://github.com/agoric-labs/moddable.git',
303
+ path: ModdableSDK.MODDABLE,
304
+ commitHash: env.MODDABLE_COMMIT_HASH,
305
+ envPrefix: 'MODDABLE_',
306
+ },
307
+ {
308
+ url:
309
+ env.XSNAP_NATIVE_URL || 'https://github.com/agoric-labs/xsnap-pub.git',
310
+ path: asset('../xsnap-native'),
311
+ commitHash: env.XSNAP_NATIVE_COMMIT_HASH,
312
+ envPrefix: 'XSNAP_NATIVE_',
313
+ },
314
+ ];
315
+
316
+ // We build both release and debug executables, so checking for only the
317
+ // former is fine.
333
318
  // XXX This will need to account for the .exe extension if we recover support
334
319
  // for Windows.
335
- const hasBin = fs.existsSync(
336
- asset(`../xsnap-native/xsnap/build/bin/${platform}/release/xsnap-worker`),
320
+ const bin = asset(
321
+ `../xsnap-native/xsnap/build/bin/${platform.path}/release/xsnap-worker`,
337
322
  );
338
- let hasSource = fs.existsSync(asset('../moddable/xs/includes/xs.h'));
323
+ const hasBin = fs.existsSync(bin);
324
+ const hasSource = fs.existsSync(asset('../moddable/xs/includes/xs.h'));
339
325
  const hasGit = fs.existsSync(asset('../moddable/.git'));
326
+
327
+ // If a git submodule is present or source files and prebuilt executables are
328
+ // both absent, consider ourselves to be in an active git checkout (as opposed
329
+ // to e.g. an extracted npm tarball).
340
330
  const isWorkingCopy = hasGit || (!hasSource && !hasBin);
341
- const showEnv = args.includes('--show-env');
342
331
 
343
- if (isWorkingCopy || showEnv) {
344
- if (showEnv && !isWorkingCopy) {
332
+ // --show-env reports submodule status without making changes.
333
+ if (args.includes('--show-env')) {
334
+ if (!isWorkingCopy) {
345
335
  throw Error('XSnap requires a working copy and git to --show-env');
346
336
  }
347
- await updateSubmodules(showEnv, { env, stdout, spawn, fs });
348
- hasSource = true;
337
+ await showEnv(submodules, { git, stdout });
338
+ return;
349
339
  }
350
340
 
351
- if (!showEnv) {
352
- if (hasSource) {
353
- // Force a rebuild if for some reason the binary is out of date
354
- // Since the make checks may not always detect that situation
355
- let forceBuild = !hasBin;
356
- if (hasBin) {
357
- const npm = makeCLI('npm', { spawn });
358
- await npm
359
- .run(['run', '-s', 'check-version'], { cwd: asset('..') })
360
- .catch(() => {
361
- forceBuild = true;
362
- });
363
- }
364
- await makeXsnap({ spawn, fs, os }, { forceBuild });
365
- } else if (!hasBin) {
366
- throw Error(
367
- 'XSnap has neither sources nor a pre-built binary. Docker? .dockerignore? npm files?',
368
- );
369
- }
341
+ // Fetch/update source files via `git submodule` as appropriate.
342
+ if (isWorkingCopy) {
343
+ await updateSubmodules(submodules, { fs, git });
344
+ }
345
+
346
+ // If we now have source files, (re)build from them.
347
+ // Otherwise, require presence of a previously-built executable.
348
+ if (hasSource || isWorkingCopy) {
349
+ // Force a rebuild if for some reason the binary is out of date
350
+ // since the make checks may not always detect that situation.
351
+ const npm = makeCLI('npm', { spawn });
352
+ const force = await (!hasBin ||
353
+ isRejected(
354
+ npm.run(['run', '-s', 'check-version'], { cwd: asset('..') }),
355
+ ));
356
+ await buildXsnap(platform, force, { fs, make });
357
+ } else if (!hasBin) {
358
+ throw Error(
359
+ 'XSnap has neither sources nor a pre-built binary. Docker? .dockerignore? npm files?',
360
+ );
370
361
  }
371
362
  }
372
363
 
@@ -376,10 +367,10 @@ const run = () =>
376
367
  stdout: process.stdout,
377
368
  spawn: childProcessTop.spawn,
378
369
  fs: {
379
- readFile: fsTop.promises.readFile,
380
- writeFile: fsTop.promises.writeFile,
381
370
  existsSync: fsTop.existsSync,
382
371
  rmdirSync: fsTop.rmdirSync,
372
+ readFile: fsTop.promises.readFile,
373
+ writeFile: fsTop.promises.writeFile,
383
374
  },
384
375
  os: {
385
376
  type: osTop.type,