libdragon 10.0.0 → 10.3.1

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.
@@ -2,12 +2,15 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
4
  const _ = require('lodash');
5
+
5
6
  const {
6
7
  LIBDRAGON_SUBMODULE,
7
8
  LIBDRAGON_BRANCH,
8
9
  LIBDRAGON_GIT,
9
- CACHED_CONTAINER_FILE,
10
+ CONTAINER_TARGET_PATH,
11
+ LIBDRAGON_PROJECT_MANIFEST,
10
12
  } = require('./constants');
13
+
11
14
  const {
12
15
  spawnProcess,
13
16
  checkContainerAndClean,
@@ -20,11 +23,14 @@ const {
20
23
  log,
21
24
  dockerExec,
22
25
  dockerRelativeWorkdirParams,
23
- dockerByteSwapParams,
24
26
  runGitMaybeHost,
25
27
  dockerHostUserParams,
28
+ dockerRelativeWorkdir,
29
+ tryCacheContainerId,
26
30
  } = require('./helpers');
27
31
 
32
+ const { printUsage } = require('./usage');
33
+
28
34
  const destroyContainer = async (libdragonInfo) => {
29
35
  if (libdragonInfo.containerId) {
30
36
  await spawnProcess('docker', [
@@ -42,12 +48,8 @@ const destroyContainer = async (libdragonInfo) => {
42
48
  };
43
49
 
44
50
  const initSubmodule = async (libdragonInfo) => {
45
- // Try to make sure submodule is there, in case it is deleted manually
46
- try {
47
- await runGitMaybeHost(libdragonInfo, ['restore', '.gitmodules']);
48
- } catch {
49
- // No need to do anything else here
50
- }
51
+ await runGitMaybeHost(libdragonInfo, ['init']);
52
+
51
53
  await runGitMaybeHost(libdragonInfo, [
52
54
  'submodule',
53
55
  'add',
@@ -62,28 +64,73 @@ const initSubmodule = async (libdragonInfo) => {
62
64
  };
63
65
 
64
66
  /**
65
- * Will donload image, create a new container and install everything in it
67
+ * Downloads the given docker image. Returns false if the local image is the
68
+ * same, new image name otherwise.
69
+ * @param libdragonInfo
70
+ * @param newImageName
71
+ * @returns false | string
72
+ */
73
+ const updateImage = async (libdragonInfo, newImageName) => {
74
+ // Will not take too much time if already have the same
75
+ const download = async () => {
76
+ libdragonInfo.showStatus &&
77
+ log(`Downloading docker image: ${newImageName}`);
78
+ await spawnProcess(
79
+ 'docker',
80
+ ['pull', newImageName],
81
+ false,
82
+ libdragonInfo.showStatus
83
+ );
84
+ };
85
+
86
+ const getDigest = async () =>
87
+ await spawnProcess(
88
+ 'docker',
89
+ ['images', '-q', '--no-trunc', newImageName],
90
+ false,
91
+ libdragonInfo.showStatus
92
+ );
93
+
94
+ // Attempt to compare digests if the new image name is the same
95
+ // Even if they are not the same tag, it is possible to have a different
96
+ // image but we already attempt a download in any case. It would just take
97
+ // less time as we already have the layers.
98
+ if (libdragonInfo.imageName === newImageName) {
99
+ const existingDigest = await getDigest();
100
+ await download();
101
+ const newDigest = await getDigest();
102
+
103
+ if (existingDigest === newDigest) {
104
+ libdragonInfo.showStatus && log(`Image is the same: ${newImageName}`);
105
+ return false;
106
+ }
107
+ } else {
108
+ await download();
109
+ }
110
+
111
+ libdragonInfo.showStatus && log(`Image is different: ${newImageName}`);
112
+ return newImageName;
113
+ };
114
+
115
+ /**
116
+ * Create a new container
66
117
  */
67
118
  const initContainer = async (libdragonInfo) => {
68
119
  let newId;
69
120
  try {
70
- const imageName = await updateImageName(libdragonInfo);
71
-
72
- // Download image
73
- libdragonInfo.showStatus && log(`Downloading docker image: ${imageName}`);
74
- await spawnProcess('docker', ['pull', imageName]);
75
-
76
121
  // Create a new container
77
122
  libdragonInfo.showStatus && log('Creating new container...');
78
123
  newId = (
79
124
  await spawnProcess('docker', [
80
125
  'run',
81
- ...dockerByteSwapParams(libdragonInfo),
82
126
  '-d', // Detached
83
127
  '--mount',
84
- 'type=bind,source=' + libdragonInfo.root + ',target=/libdragon', // Mount files
85
- '-w=/libdragon', // Set working directory
86
- imageName,
128
+ 'type=bind,source=' +
129
+ libdragonInfo.root +
130
+ ',target=' +
131
+ CONTAINER_TARGET_PATH, // Mount files
132
+ '-w=' + CONTAINER_TARGET_PATH, // Set working directory
133
+ libdragonInfo.imageName,
87
134
  'tail',
88
135
  '-f',
89
136
  '/dev/null',
@@ -93,7 +140,6 @@ const initContainer = async (libdragonInfo) => {
93
140
  const newInfo = {
94
141
  ...libdragonInfo,
95
142
  containerId: newId,
96
- imageName,
97
143
  };
98
144
 
99
145
  // chown the installation folder once on init
@@ -104,11 +150,6 @@ const initContainer = async (libdragonInfo) => {
104
150
  `${uid >= 0 ? uid : ''}:${gid >= 0 ? gid : ''}`,
105
151
  '/n64_toolchain',
106
152
  ]);
107
-
108
- await runGitMaybeHost(libdragonInfo, ['init']);
109
- await initSubmodule(libdragonInfo);
110
-
111
- await installDependencies(newInfo);
112
153
  } catch (e) {
113
154
  // Dispose the invalid container, clean and exit
114
155
  await destroyContainer({
@@ -131,15 +172,13 @@ const initContainer = async (libdragonInfo) => {
131
172
  '{{.Name}}',
132
173
  ]);
133
174
 
134
- // We have created a new container, save the new info
135
- fs.writeFileSync(
136
- path.join(libdragonInfo.root, '.git', CACHED_CONTAINER_FILE),
137
- newId
138
- );
139
175
  libdragonInfo.showStatus &&
140
176
  log(
141
177
  chalk.green(`Successfully initialized docker container: ${name.trim()}`)
142
178
  );
179
+
180
+ // Update the image name only after everything is OK
181
+ await updateImageName(libdragonInfo);
143
182
  return newId;
144
183
  };
145
184
 
@@ -147,12 +186,15 @@ const initContainer = async (libdragonInfo) => {
147
186
  * Recursively copies directories and files
148
187
  */
149
188
  function copyDirContents(src, dst) {
189
+ log(`Copying from ${src} to ${dst}`, true);
190
+
150
191
  if (fs.existsSync(dst) && !fs.statSync(dst).isDirectory()) {
151
192
  log(`${dst} is not a directory, skipping.`);
152
193
  return;
153
194
  }
154
195
 
155
196
  if (!fs.existsSync(dst)) {
197
+ log(`Creating a directory at ${dst}.`, true);
156
198
  fs.mkdirSync(dst);
157
199
  }
158
200
 
@@ -166,6 +208,7 @@ function copyDirContents(src, dst) {
166
208
  } else if (stats.isFile()) {
167
209
  const content = fs.readFileSync(source);
168
210
  try {
211
+ log(`Writing to ${dest}`, true);
169
212
  fs.writeFileSync(dest, content, {
170
213
  flag: 'wx',
171
214
  });
@@ -179,22 +222,72 @@ function copyDirContents(src, dst) {
179
222
 
180
223
  /**
181
224
  * Initialize a new libdragon project in current working directory
225
+ * Also downloads the image
182
226
  */
183
227
  async function init(libdragonInfo) {
184
- log(`Initializing a libdragon project at ${libdragonInfo.root}.`);
185
- const isNewProject = await createManifestIfNotExist(libdragonInfo);
228
+ log(`Initializing a libdragon project at ${libdragonInfo.root}`);
229
+
230
+ const files = fs.readdirSync(libdragonInfo.root);
231
+
232
+ const manifestFile = files.find(
233
+ (name) => name === LIBDRAGON_PROJECT_MANIFEST
234
+ );
235
+
236
+ if (manifestFile) {
237
+ log(
238
+ `${path.join(
239
+ libdragonInfo.root,
240
+ manifestFile
241
+ )} exists. This is already a libdragon project, starting it...`
242
+ );
243
+ if (libdragonInfo.options.DOCKER_IMAGE) {
244
+ log(
245
+ `Not changing docker image. Use the install action if you want to override the image.`
246
+ );
247
+ }
248
+ await install(libdragonInfo);
249
+ return;
250
+ }
186
251
 
187
- log(`Preparing the docker container...`);
188
- await start(libdragonInfo);
252
+ const libdragonFile = files.find((name) =>
253
+ name.match(new RegExp(`^${LIBDRAGON_SUBMODULE}.?`))
254
+ );
189
255
 
190
- if (isNewProject) {
191
- log(`Copying project files...`);
192
- const skeletonFolder = path.join(__dirname, '../skeleton');
193
- // node copy functions does not work with pkg
194
- copyDirContents(skeletonFolder, libdragonInfo.root);
256
+ if (libdragonFile) {
257
+ throw new Error(
258
+ `${path.join(
259
+ libdragonInfo.root,
260
+ libdragonFile
261
+ )} already exists. That is the libdragon vendoring target, please remove and retry.`
262
+ );
195
263
  }
196
264
 
197
- log(chalk.green(`libdragon ready at \`${libdragonInfo.root}\`.`));
265
+ await createManifestIfNotExist(libdragonInfo);
266
+ // Download image and start it
267
+ const newId = await start({
268
+ ...libdragonInfo,
269
+ imageName:
270
+ (await updateImage(libdragonInfo, libdragonInfo.imageName)) ||
271
+ libdragonInfo.imageName,
272
+ });
273
+ const newInfo = {
274
+ ...libdragonInfo,
275
+ containerId: newId,
276
+ };
277
+
278
+ await initSubmodule(newInfo);
279
+
280
+ // We have created a new container, save the new info
281
+ tryCacheContainerId(newInfo);
282
+
283
+ await installDependencies(newInfo);
284
+
285
+ log(`Copying project files...`);
286
+ const skeletonFolder = path.join(__dirname, '../skeleton');
287
+ // node copy functions does not work with pkg
288
+ copyDirContents(skeletonFolder, newInfo.root);
289
+
290
+ log(chalk.green(`libdragon ready at \`${newInfo.root}\`.`));
198
291
  }
199
292
 
200
293
  const start = async (libdragonInfo) => {
@@ -217,12 +310,8 @@ const start = async (libdragonInfo) => {
217
310
  return id;
218
311
  }
219
312
 
220
- log(`Starting container: ${libdragonInfo.containerId}`, true);
221
- await spawnProcess('docker', [
222
- 'container',
223
- 'start',
224
- libdragonInfo.containerId,
225
- ]);
313
+ log(`Starting container: ${id}`, true);
314
+ await spawnProcess('docker', ['container', 'start', id]);
226
315
 
227
316
  log(id);
228
317
  return id;
@@ -239,29 +328,33 @@ const stop = async (libdragonInfo) => {
239
328
  await spawnProcess('docker', ['container', 'stop', running]);
240
329
  };
241
330
 
242
- const make = async (libdragonInfo, params) => {
243
- const workdir = toPosixPath(path.relative(libdragonInfo.root, process.cwd()));
244
- log(`Running make at ${workdir ?? '.'} with [${params}]`, true);
331
+ const exec = async (libdragonInfo, commandAndParams) => {
332
+ log(
333
+ `Running ${commandAndParams[0]} at ${dockerRelativeWorkdir(
334
+ libdragonInfo
335
+ )} with [${commandAndParams.slice(1)}]`,
336
+ true
337
+ );
245
338
 
246
- const tryMake = (libdragonInfo) =>
339
+ const tryCmd = (libdragonInfo) =>
247
340
  libdragonInfo.containerId &&
248
341
  dockerExec(
249
342
  libdragonInfo,
250
343
  [
251
344
  ...dockerRelativeWorkdirParams(libdragonInfo),
252
- ...dockerByteSwapParams(libdragonInfo),
253
345
  ...dockerHostUserParams(libdragonInfo),
254
346
  ],
255
- ['make', ...params],
256
- true
347
+ commandAndParams,
348
+ true,
349
+ true // Cannot use "full" here, we need to know if the container is alive
257
350
  );
258
351
 
259
352
  let started = false;
260
- const startOnceAndMake = async () => {
353
+ const startOnceAndCmd = async () => {
261
354
  if (!started) {
262
355
  const newId = await start(libdragonInfo);
263
356
  started = true;
264
- await tryMake({
357
+ await tryCmd({
265
358
  ...libdragonInfo,
266
359
  containerId: newId,
267
360
  });
@@ -271,12 +364,12 @@ const make = async (libdragonInfo, params) => {
271
364
 
272
365
  if (!libdragonInfo.containerId) {
273
366
  log(`Container does not exist for sure, restart`, true);
274
- await startOnceAndMake();
367
+ await startOnceAndCmd();
275
368
  return;
276
369
  }
277
370
 
278
371
  try {
279
- await tryMake(libdragonInfo);
372
+ await tryCmd(libdragonInfo);
280
373
  } catch (e) {
281
374
  if (
282
375
  !e.out ||
@@ -285,19 +378,36 @@ const make = async (libdragonInfo, params) => {
285
378
  ) {
286
379
  throw e;
287
380
  }
288
- await startOnceAndMake();
381
+ await startOnceAndCmd();
289
382
  }
290
383
  };
291
384
 
385
+ const make = async (libdragonInfo, params) => {
386
+ await exec(libdragonInfo, ['make', ...params]);
387
+ };
388
+
292
389
  const installDependencies = async (libdragonInfo) => {
293
- libdragonInfo.showStatus && log('Vendoring libdragon...');
390
+ const buildScriptPath = path.join(
391
+ libdragonInfo.root,
392
+ 'libdragon',
393
+ 'build.sh'
394
+ );
395
+ if (
396
+ !fs.existsSync(buildScriptPath) ||
397
+ !fs.statSync(buildScriptPath).isFile()
398
+ ) {
399
+ throw new Error(
400
+ 'build.sh not found. Make sure you have a vendored libdragon copy at ./libdragon'
401
+ );
402
+ }
403
+
404
+ libdragonInfo.showStatus && log('Installing libdragon to the container...');
294
405
 
295
406
  await dockerExec(
296
407
  libdragonInfo,
297
408
  [
298
409
  '--workdir',
299
- '/libdragon/' + LIBDRAGON_SUBMODULE,
300
- ...dockerByteSwapParams(libdragonInfo),
410
+ CONTAINER_TARGET_PATH + '/' + LIBDRAGON_SUBMODULE,
301
411
  ...dockerHostUserParams(libdragonInfo),
302
412
  ],
303
413
  ['/bin/bash', './build.sh']
@@ -348,7 +458,10 @@ const installDependencies = async (libdragonInfo) => {
348
458
  const relativePath = toPosixPath(
349
459
  path.relative(libdragonInfo.root, paths[0])
350
460
  );
351
- const containerPath = path.posix.join('/libdragon', relativePath);
461
+ const containerPath = path.posix.join(
462
+ CONTAINER_TARGET_PATH,
463
+ relativePath
464
+ );
352
465
  const makePath = path.posix.join(containerPath, 'Makefile');
353
466
 
354
467
  await dockerExec(
@@ -378,52 +491,29 @@ const installDependencies = async (libdragonInfo) => {
378
491
  }
379
492
  };
380
493
 
381
- const requiresContainer = async (libdragonInfo) => {
382
- const id = await checkContainerAndClean(libdragonInfo);
383
-
384
- if (!id) {
385
- throw new Error(
386
- 'libdragon is not properly initialized. Initialize with `libdragon init` first.'
387
- );
388
- }
389
-
390
- return id;
391
- };
392
-
393
- const maybeStartNewImage = async (libdragonInfo) => {
394
- let containerId = libdragonInfo.containerId;
395
- const oldImageName = libdragonInfo.imageName;
396
- const imageName = await updateImageName(libdragonInfo);
397
- if (oldImageName !== imageName) {
398
- log(`Changing image from \`${oldImageName}\` to \`${imageName}\``);
399
- await destroyContainer(libdragonInfo);
400
-
401
- // Start the new image
402
- containerId = await start({
403
- ...libdragonInfo,
404
- imageName,
405
- });
406
- }
494
+ const update = async (libdragonInfo) => {
495
+ const containerId = await start(libdragonInfo);
407
496
 
408
- return {
497
+ const newInfo = {
409
498
  ...libdragonInfo,
410
- imageName,
411
499
  containerId,
412
500
  };
413
- };
414
-
415
- const update = async (libdragonInfo) => {
416
- let containerId = await requiresContainer(libdragonInfo);
417
-
418
- // Start existing
419
- containerId = await start(libdragonInfo);
420
501
 
421
502
  // Update submodule
422
503
  log('Updating submodule...');
423
504
 
424
- await initSubmodule(libdragonInfo);
505
+ try {
506
+ await initSubmodule(newInfo);
507
+ } catch {
508
+ throw new Error(
509
+ `Unable to re-initialize vendored libdragon. Probably git does not know the vendoring target (${path.join(
510
+ libdragonInfo.root,
511
+ LIBDRAGON_SUBMODULE
512
+ )}) Removing it might resolve this issue.`
513
+ );
514
+ }
425
515
 
426
- await runGitMaybeHost(libdragonInfo, [
516
+ await runGitMaybeHost(newInfo, [
427
517
  'submodule',
428
518
  'update',
429
519
  '--remote',
@@ -431,72 +521,91 @@ const update = async (libdragonInfo) => {
431
521
  './' + LIBDRAGON_SUBMODULE,
432
522
  ]);
433
523
 
434
- await install({
435
- ...libdragonInfo,
436
- containerId,
437
- });
524
+ await install(newInfo);
438
525
  };
439
526
 
527
+ /**
528
+ * Updates the image if flag is provided and install vendors onto the container.
529
+ * We should probably remove the image installation responsibility from this
530
+ * action but it might be a breaking change. Maybe we can keep it backward
531
+ * compatible with additional flags.
532
+ * @param libdragonInfo
533
+ */
440
534
  const install = async (libdragonInfo) => {
441
- let containerId = await requiresContainer(libdragonInfo);
535
+ let containerId;
536
+ const oldImageName = libdragonInfo.imageName;
537
+ const imageName = libdragonInfo.options.DOCKER_IMAGE;
538
+ // If an image is provided, always attempt to install it
539
+ // See https://github.com/anacierdem/libdragon-docker/issues/47
540
+ if (imageName) {
541
+ log(`Changing image from \`${oldImageName}\` to \`${imageName}\``);
542
+
543
+ // Download the new image and if it is different, re-create the container
544
+ if (await updateImage(libdragonInfo, imageName)) {
545
+ await destroyContainer(libdragonInfo);
546
+ }
442
547
 
443
- const newInfo = await maybeStartNewImage({
548
+ containerId = await start({
549
+ ...libdragonInfo,
550
+ imageName,
551
+ });
552
+ } else {
553
+ // Make sure existing one is running
554
+ containerId = await start(libdragonInfo);
555
+ }
556
+
557
+ // Re-install vendors on new image
558
+ // TODO: skip this if unnecessary
559
+ await installDependencies({
444
560
  ...libdragonInfo,
561
+ imageName,
445
562
  containerId,
446
563
  });
447
-
448
- // Re-install vendors on new image
449
- await installDependencies(newInfo);
450
- };
451
-
452
- const help = () => {
453
- log(`${chalk.green('Usage:')}
454
- libdragon [flags] <action>
455
-
456
- ${chalk.green('Available Commands:')}
457
- help Display this help information.
458
- init Create a libdragon project in the current directory.
459
- make Run the libdragon build system in the current directory.
460
- start Start the container for current project.
461
- stop Stop the container for current project.
462
- install Vendor libdragon as is.
463
- update Update libdragon and do an install.
464
-
465
- ${chalk.green('Flags:')}
466
- --image <docker-image> Provide a custom image.
467
- --byte-swap Enable byte-swapped ROM output.
468
- --verbose Be verbose
469
- `);
470
564
  };
471
565
 
566
+ // TODO: separate into files
472
567
  module.exports = {
473
568
  start: {
569
+ name: 'start',
474
570
  fn: start,
475
571
  showStatus: false, // This will only print out the id
476
572
  },
477
573
  init: {
574
+ name: 'init',
478
575
  fn: init,
479
576
  showStatus: true,
480
577
  },
481
578
  make: {
579
+ name: 'make',
482
580
  fn: make,
483
581
  forwardsRestParams: true,
484
582
  showStatus: true,
485
583
  },
584
+ exec: {
585
+ name: 'exec',
586
+ fn: exec,
587
+ forwardsRestParams: true,
588
+ showStatus: true,
589
+ },
486
590
  stop: {
591
+ name: 'stop',
487
592
  fn: stop,
488
593
  showStatus: false, // This will only print out the id
489
594
  },
490
595
  install: {
596
+ name: 'install',
491
597
  fn: install,
492
598
  showStatus: true,
493
599
  },
494
600
  update: {
601
+ name: 'update',
495
602
  fn: update,
496
603
  showStatus: true,
497
604
  },
498
605
  help: {
499
- fn: help,
606
+ name: 'help',
607
+ fn: printUsage,
500
608
  showStatus: true,
609
+ forwardsRestParams: true,
501
610
  },
502
611
  };
@@ -5,6 +5,7 @@ module.exports = {
5
5
  LIBDRAGON_GIT: 'https://github.com/DragonMinded/libdragon',
6
6
  LIBDRAGON_BRANCH: 'trunk',
7
7
  LIBDRAGON_SUBMODULE: 'libdragon',
8
+ CONTAINER_TARGET_PATH: '/libdragon',
8
9
 
9
10
  LIBDRAGON_PROJECT_MANIFEST: '.libdragon',
10
11
  CACHED_CONTAINER_FILE: 'libdragon-docker-container',