libdragon 10.3.1 → 10.4.2

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.
@@ -0,0 +1,201 @@
1
+ const fs = require('fs/promises');
2
+ const path = require('path');
3
+
4
+ const chalk = require('chalk');
5
+
6
+ const { fn: install } = require('./install');
7
+ const { start } = require('./start');
8
+ const {
9
+ installDependencies,
10
+ tryCacheContainerId,
11
+ updateImage,
12
+ runGitMaybeHost,
13
+ } = require('./utils');
14
+
15
+ const {
16
+ LIBDRAGON_PROJECT_MANIFEST,
17
+ LIBDRAGON_SUBMODULE,
18
+ LIBDRAGON_BRANCH,
19
+ LIBDRAGON_GIT,
20
+ } = require('../constants');
21
+ const {
22
+ log,
23
+ copyDirContents,
24
+ CommandError,
25
+ ParameterError,
26
+ ValidationError,
27
+ } = require('../helpers');
28
+ const { setProjectInfoToSave } = require('../project-info');
29
+
30
+ const autoVendor = async (libdragonInfo) => {
31
+ await runGitMaybeHost(libdragonInfo, ['init']);
32
+
33
+ if (libdragonInfo.vendorStrategy === 'submodule') {
34
+ await runGitMaybeHost(libdragonInfo, [
35
+ 'submodule',
36
+ 'add',
37
+ '--force',
38
+ '--name',
39
+ LIBDRAGON_SUBMODULE,
40
+ '--branch',
41
+ LIBDRAGON_BRANCH,
42
+ LIBDRAGON_GIT,
43
+ libdragonInfo.vendorDirectory,
44
+ ]);
45
+ } else if (libdragonInfo.vendorStrategy === 'subtree') {
46
+ // Create a commit if it does not exist. This is required for subtree.
47
+ try {
48
+ await runGitMaybeHost(libdragonInfo, ['rev-parse', 'HEAD']);
49
+ } catch (e) {
50
+ if (!(e instanceof CommandError)) throw e;
51
+
52
+ // This will throw if git user name/email is not set up. Let's not assume
53
+ // anything for now. This means subtree is not supported for someone without
54
+ // git on the host machine.
55
+ await runGitMaybeHost(libdragonInfo, [
56
+ 'commit',
57
+ '--allow-empty',
58
+ '-n',
59
+ '-m',
60
+ 'Initial commit.',
61
+ ]);
62
+ }
63
+
64
+ await runGitMaybeHost(libdragonInfo, [
65
+ 'subtree',
66
+ 'add',
67
+ '--prefix',
68
+ path.relative(libdragonInfo.root, libdragonInfo.vendorDirectory),
69
+ LIBDRAGON_GIT,
70
+ LIBDRAGON_BRANCH,
71
+ '--squash',
72
+ ]);
73
+ }
74
+
75
+ return libdragonInfo;
76
+ };
77
+
78
+ /**
79
+ * Initialize a new libdragon project in current working directory
80
+ * Also downloads the image
81
+ */
82
+ async function init(libdragonInfo) {
83
+ log(`Initializing a libdragon project at ${libdragonInfo.root}`);
84
+
85
+ let newInfo = libdragonInfo;
86
+
87
+ // Validate manifest
88
+ const manifestPath = path.join(newInfo.root, LIBDRAGON_PROJECT_MANIFEST);
89
+ const manifestStats = await fs.stat(manifestPath).catch((e) => {
90
+ if (e.code !== 'ENOENT') throw e;
91
+ return false;
92
+ });
93
+
94
+ if (manifestStats && !manifestStats.isDirectory()) {
95
+ throw new ValidationError(
96
+ 'There is already a `.libdragon` file and it is not a directory.'
97
+ );
98
+ }
99
+
100
+ // Validate vendoring strategy. Do not allow a switch after successful initialization
101
+ if (
102
+ newInfo.haveProjectConfig &&
103
+ newInfo.options.VENDOR_STRAT &&
104
+ newInfo.options.VENDOR_STRAT !== 'manual' &&
105
+ newInfo.vendorStrategy !== newInfo.options.VENDOR_STRAT
106
+ ) {
107
+ throw new ParameterError(
108
+ `Requested strategy switch: ${newInfo.vendorStrategy} -> ${newInfo.options.VENDOR_STRAT} It is not possible to switch vendoring strategy after initializing a project. You can always switch to manual and handle libdragon yourself.`
109
+ );
110
+ }
111
+
112
+ // Update the strategy information for the project if the flag is provided
113
+ if (newInfo.options.VENDOR_STRAT) {
114
+ newInfo = setProjectInfoToSave({
115
+ ...newInfo,
116
+ vendorStrategy: newInfo.options.VENDOR_STRAT,
117
+ });
118
+ }
119
+
120
+ // Update the directory information for the project if the flag is provided
121
+ if (newInfo.options.VENDOR_DIR) {
122
+ // Validate vendoring path
123
+ if (path.isAbsolute(newInfo.options.VENDOR_DIR)) {
124
+ throw new ParameterError(
125
+ '`--directory` must be project-relative as it will be mounted to the docker container.'
126
+ );
127
+ }
128
+
129
+ newInfo = setProjectInfoToSave({
130
+ ...newInfo,
131
+ vendorDirectory: newInfo.options.VENDOR_DIR,
132
+ });
133
+ }
134
+
135
+ if (newInfo.haveProjectConfig) {
136
+ log(
137
+ `${path.join(
138
+ newInfo.root,
139
+ LIBDRAGON_PROJECT_MANIFEST
140
+ )} exists. This is already a libdragon project, starting it...`
141
+ );
142
+ if (newInfo.options.DOCKER_IMAGE) {
143
+ log(
144
+ `Not changing docker image. Use the install action if you want to override the image.`
145
+ );
146
+ }
147
+ // TODO: we may make sure git and submodule is initialized here
148
+ await install(newInfo);
149
+ return;
150
+ }
151
+
152
+ newInfo.imageName =
153
+ (await updateImage(newInfo, newInfo.imageName)) || newInfo.imageName;
154
+ // Download image and start it
155
+ const containerReadyPromise = start(newInfo, true).then((newId) => ({
156
+ ...newInfo,
157
+ containerId: newId,
158
+ }));
159
+
160
+ let vendorAndGitReadyPromise = containerReadyPromise;
161
+ if (newInfo.vendorStrategy !== 'manual') {
162
+ const vendorTarget = path.relative(newInfo.root, newInfo.vendorDirectory);
163
+ const [vendorTargetExists] = await Promise.all([
164
+ fs.stat(vendorTarget).catch((e) => {
165
+ if (e.code !== 'ENOENT') throw e;
166
+ return false;
167
+ }),
168
+ containerReadyPromise,
169
+ ]);
170
+
171
+ if (vendorTargetExists) {
172
+ throw new ValidationError(
173
+ `${path.resolve(
174
+ newInfo.root,
175
+ newInfo.vendorDirectory
176
+ )} already exists. That is the libdragon vendoring target, please remove and retry.`
177
+ );
178
+ }
179
+
180
+ vendorAndGitReadyPromise = containerReadyPromise.then(autoVendor);
181
+ }
182
+
183
+ log(`Preparing project files...`);
184
+ const skeletonFolder = path.join(__dirname, '../../skeleton');
185
+
186
+ await Promise.all([
187
+ // We have created a new container, save the new info
188
+ vendorAndGitReadyPromise.then(tryCacheContainerId),
189
+ vendorAndGitReadyPromise.then(installDependencies),
190
+ // node copy functions does not work with pkg
191
+ copyDirContents(skeletonFolder, newInfo.root),
192
+ ]);
193
+
194
+ log(chalk.green(`libdragon ready at \`${newInfo.root}\`.`));
195
+ }
196
+
197
+ module.exports = {
198
+ name: 'init',
199
+ fn: init,
200
+ showStatus: true,
201
+ };
@@ -0,0 +1,48 @@
1
+ const chalk = require('chalk');
2
+
3
+ const { installDependencies, mustHaveProject } = require('./utils');
4
+ const { start } = require('./start');
5
+ const { updateAndStart } = require('./update-and-start');
6
+ const { log } = require('../helpers');
7
+
8
+ /**
9
+ * Updates the image if flag is provided and install vendors onto the container.
10
+ * We should probably remove the image installation responsibility from this
11
+ * action but it might be a breaking change.
12
+ * @param libdragonInfo
13
+ * @param skipUpdate This is added to skip the update when calling this from
14
+ * the update action as it already does an update itself. install doing an image
15
+ * update is pretty much a useless operation, but let's keep it in case someone
16
+ * depends on it. It used to only update the image if the flag is provided and
17
+ * we still keep that logic but with a deprecation warning.
18
+ */
19
+ const install = async (libdragonInfo, skipUpdate) => {
20
+ await mustHaveProject(libdragonInfo);
21
+ let updatedInfo = libdragonInfo;
22
+ const imageName = libdragonInfo.options.DOCKER_IMAGE;
23
+ // If an image is provided, attempt to install
24
+ if (imageName && skipUpdate !== true) {
25
+ log(
26
+ chalk.yellow(
27
+ 'Using `install` action to update the docker image is deprecated. Use the `update` action instead.'
28
+ )
29
+ );
30
+ updatedInfo = await updateAndStart(libdragonInfo);
31
+ } else {
32
+ // Make sure existing one is running
33
+ updatedInfo = {
34
+ ...updatedInfo,
35
+ containerId: await start(libdragonInfo),
36
+ };
37
+ }
38
+
39
+ // Re-install vendors on new image
40
+ // TODO: skip this if unnecessary
41
+ await installDependencies(updatedInfo);
42
+ };
43
+
44
+ module.exports = {
45
+ name: 'install',
46
+ fn: install,
47
+ showStatus: true,
48
+ };
@@ -0,0 +1,114 @@
1
+ const chalk = require('chalk');
2
+
3
+ const { CONTAINER_TARGET_PATH } = require('../constants');
4
+ const { spawnProcess, log, dockerExec } = require('../helpers');
5
+ const { setProjectInfoToSave } = require('../project-info');
6
+
7
+ const {
8
+ checkContainerAndClean,
9
+ checkContainerRunning,
10
+ destroyContainer,
11
+ mustHaveProject,
12
+ } = require('./utils');
13
+
14
+ /**
15
+ * Create a new container
16
+ */
17
+ const initContainer = async (libdragonInfo) => {
18
+ let newId;
19
+ try {
20
+ // Create a new container
21
+ libdragonInfo.showStatus && log('Creating new container...');
22
+ newId = (
23
+ await spawnProcess('docker', [
24
+ 'run',
25
+ '-d', // Detached
26
+ '--mount',
27
+ 'type=bind,source=' +
28
+ libdragonInfo.root +
29
+ ',target=' +
30
+ CONTAINER_TARGET_PATH, // Mount files
31
+ '-w=' + CONTAINER_TARGET_PATH, // Set working directory
32
+ libdragonInfo.imageName,
33
+ 'tail',
34
+ '-f',
35
+ '/dev/null',
36
+ ])
37
+ ).trim();
38
+
39
+ const newInfo = {
40
+ ...libdragonInfo,
41
+ containerId: newId,
42
+ };
43
+
44
+ // chown the installation folder once on init
45
+ const { uid, gid } = libdragonInfo.userInfo;
46
+ await dockerExec(newInfo, [
47
+ 'chown',
48
+ '-R',
49
+ `${uid >= 0 ? uid : ''}:${gid >= 0 ? gid : ''}`,
50
+ '/n64_toolchain',
51
+ ]);
52
+ } catch (e) {
53
+ // Dispose the invalid container, clean and exit
54
+ await destroyContainer({
55
+ ...libdragonInfo,
56
+ containerId: newId,
57
+ });
58
+ log(
59
+ chalk.red(
60
+ 'We were unable to initialize libdragon. Done cleanup. Check following logs for the actual error.'
61
+ )
62
+ );
63
+ throw e;
64
+ }
65
+
66
+ const name = await spawnProcess('docker', [
67
+ 'container',
68
+ 'inspect',
69
+ newId,
70
+ '--format',
71
+ '{{.Name}}',
72
+ ]);
73
+
74
+ libdragonInfo.showStatus &&
75
+ log(
76
+ chalk.green(`Successfully initialized docker container: ${name.trim()}`)
77
+ );
78
+
79
+ // Schedule an update to write image name
80
+ setProjectInfoToSave(libdragonInfo);
81
+ return newId;
82
+ };
83
+
84
+ const start = async (libdragonInfo, skipProjectCheck) => {
85
+ !skipProjectCheck && (await mustHaveProject(libdragonInfo));
86
+ const running =
87
+ libdragonInfo.containerId &&
88
+ (await checkContainerRunning(libdragonInfo.containerId));
89
+
90
+ if (running) {
91
+ log(`Container ${running} already running.`, true);
92
+ return running;
93
+ }
94
+
95
+ let id = await checkContainerAndClean(libdragonInfo);
96
+
97
+ if (!id) {
98
+ log(`Container does not exist, re-initializing...`, true);
99
+ id = await initContainer(libdragonInfo);
100
+ return id;
101
+ }
102
+
103
+ log(`Starting container: ${id}`, true);
104
+ await spawnProcess('docker', ['container', 'start', id]);
105
+
106
+ return id;
107
+ };
108
+
109
+ module.exports = {
110
+ name: 'start',
111
+ fn: async (libdragonInfo) => log(await start(libdragonInfo)),
112
+ start,
113
+ showStatus: false, // This will only print out the id
114
+ };
@@ -0,0 +1,21 @@
1
+ const { spawnProcess } = require('../helpers');
2
+
3
+ const { checkContainerRunning, mustHaveProject } = require('./utils');
4
+
5
+ const stop = async (libdragonInfo) => {
6
+ await mustHaveProject(libdragonInfo);
7
+ const running =
8
+ libdragonInfo.containerId &&
9
+ (await checkContainerRunning(libdragonInfo.containerId));
10
+ if (!running) {
11
+ return;
12
+ }
13
+
14
+ await spawnProcess('docker', ['container', 'stop', running]);
15
+ };
16
+
17
+ module.exports = {
18
+ name: 'stop',
19
+ fn: stop,
20
+ showStatus: false, // This will only print out the id
21
+ };
@@ -0,0 +1,33 @@
1
+ const { log } = require('../helpers');
2
+ const { updateImage, destroyContainer } = require('./utils');
3
+ const { start } = require('./start');
4
+
5
+ async function updateAndStart(libdragonInfo) {
6
+ const oldImageName = libdragonInfo.imageName;
7
+ const imageName = libdragonInfo.options.DOCKER_IMAGE ?? oldImageName;
8
+ // If an image is provided, always attempt to install it
9
+ // See https://github.com/anacierdem/libdragon-docker/issues/47
10
+ if (oldImageName !== imageName) {
11
+ log(`Updating image from \`${oldImageName}\` to \`${imageName}\``);
12
+ } else {
13
+ log(`Updating image \`${oldImageName}\``);
14
+ }
15
+
16
+ // Download the new image and if it is different, re-create the container
17
+ if (await updateImage(libdragonInfo, imageName)) {
18
+ await destroyContainer(libdragonInfo);
19
+ }
20
+
21
+ return {
22
+ ...libdragonInfo,
23
+ imageName,
24
+ containerId: await start({
25
+ ...libdragonInfo,
26
+ imageName,
27
+ }),
28
+ };
29
+ }
30
+
31
+ module.exports = {
32
+ updateAndStart,
33
+ };
@@ -0,0 +1,46 @@
1
+ const path = require('path');
2
+
3
+ const { log } = require('../helpers');
4
+ const { LIBDRAGON_GIT, LIBDRAGON_BRANCH } = require('../constants');
5
+ const { runGitMaybeHost, mustHaveProject } = require('./utils');
6
+ const { fn: install } = require('./install');
7
+ const { updateAndStart } = require('./update-and-start');
8
+
9
+ const update = async (libdragonInfo) => {
10
+ await mustHaveProject(libdragonInfo);
11
+ const newInfo = await updateAndStart(libdragonInfo);
12
+
13
+ if (newInfo.vendorStrategy !== 'manual') {
14
+ log(`Updating ${newInfo.vendorStrategy}...`);
15
+ }
16
+
17
+ if (newInfo.vendorStrategy === 'submodule') {
18
+ await runGitMaybeHost(newInfo, [
19
+ 'submodule',
20
+ 'update',
21
+ '--remote',
22
+ '--merge',
23
+ newInfo.vendorDirectory,
24
+ ]);
25
+ } else if (newInfo.vendorStrategy === 'subtree') {
26
+ await runGitMaybeHost(newInfo, [
27
+ 'subtree',
28
+ 'pull',
29
+ '--prefix',
30
+ path.relative(libdragonInfo.root, libdragonInfo.vendorDirectory),
31
+ LIBDRAGON_GIT,
32
+ LIBDRAGON_BRANCH,
33
+ '--squash',
34
+ ]);
35
+ }
36
+
37
+ // The second parameter forces it to skip the image update step as we already
38
+ // do that above.
39
+ await install(newInfo, true);
40
+ };
41
+
42
+ module.exports = {
43
+ name: 'update',
44
+ fn: update,
45
+ showStatus: true,
46
+ };