UNPKG

7.16 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
6
7var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
8
9const path = require('path');
10
11const commandExists = require('command-exists');
12
13const childProcess = require('child_process');
14
15const _require = require('@parcel/utils'),
16 promisify = _require.promisify;
17
18const exec = promisify(childProcess.execFile);
19
20const toml = require('@iarna/toml');
21
22const fs = require('@parcel/fs');
23
24const Asset = require('../Asset');
25
26const config = require('../utils/config');
27
28const pipeSpawn = require('../utils/pipeSpawn');
29
30const md5 = require('../utils/md5');
31
32const RUST_TARGET = 'wasm32-unknown-unknown';
33const MAIN_FILES = ['src/lib.rs', 'src/main.rs']; // Track installation status so we don't need to check more than once
34
35let rustInstalled = false;
36
37class RustAsset extends Asset {
38 constructor(name, options) {
39 super(name, options);
40 this.type = 'wasm';
41 }
42
43 process() {
44 // We don't want to process this asset if the worker is in a warm up phase
45 // since the asset will also be processed by the main process, which
46 // may cause errors since rust writes to the filesystem.
47 if (this.options.isWarmUp) {
48 return;
49 }
50
51 return super.process();
52 }
53
54 parse() {
55 var _this = this;
56
57 return (0, _asyncToGenerator2.default)(function* () {
58 // Install rust toolchain and target if needed
59 yield _this.installRust(); // See if there is a Cargo config in the project
60
61 let cargoConfig = yield _this.getConfig(['Cargo.toml']);
62 let cargoDir;
63 let isMainFile = false;
64
65 if (cargoConfig) {
66 const mainFiles = MAIN_FILES.slice();
67
68 if (cargoConfig.lib && cargoConfig.lib.path) {
69 mainFiles.push(cargoConfig.lib.path);
70 }
71
72 cargoDir = path.dirname((yield config.resolve(_this.name, ['Cargo.toml'])));
73 isMainFile = mainFiles.some(file => path.join(cargoDir, file) === _this.name);
74 } // If this is the main file of a Cargo build, use the cargo command to compile.
75 // Otherwise, use rustc directly.
76
77
78 if (isMainFile) {
79 yield _this.cargoBuild(cargoConfig, cargoDir);
80 } else {
81 yield _this.rustcBuild();
82 }
83 })();
84 }
85
86 installRust() {
87 return (0, _asyncToGenerator2.default)(function* () {
88 if (rustInstalled) {
89 return;
90 } // Check for rustup
91
92
93 try {
94 yield commandExists('rustup');
95 } catch (e) {
96 throw new Error("Rust isn't installed. Visit https://www.rustup.rs/ for more info");
97 } // Ensure nightly toolchain is installed
98
99
100 let _ref = yield exec('rustup', ['show']),
101 _ref2 = (0, _slicedToArray2.default)(_ref, 1),
102 stdout = _ref2[0];
103
104 if (!stdout.includes('nightly')) {
105 yield pipeSpawn('rustup', ['update']);
106 yield pipeSpawn('rustup', ['toolchain', 'install', 'nightly']);
107 } // Ensure wasm target is installed
108
109
110 var _ref3 = yield exec('rustup', ['target', 'list', '--toolchain', 'nightly']);
111
112 var _ref4 = (0, _slicedToArray2.default)(_ref3, 1);
113
114 stdout = _ref4[0];
115
116 if (!stdout.includes(RUST_TARGET + ' (installed)')) {
117 yield pipeSpawn('rustup', ['target', 'add', RUST_TARGET, '--toolchain', 'nightly']);
118 }
119
120 rustInstalled = true;
121 })();
122 }
123
124 cargoBuild(cargoConfig, cargoDir) {
125 var _this2 = this;
126
127 return (0, _asyncToGenerator2.default)(function* () {
128 // Ensure the cargo config has cdylib as the crate-type
129 if (!cargoConfig.lib) {
130 cargoConfig.lib = {};
131 }
132
133 if (!Array.isArray(cargoConfig.lib['crate-type'])) {
134 cargoConfig.lib['crate-type'] = [];
135 }
136
137 if (!cargoConfig.lib['crate-type'].includes('cdylib')) {
138 cargoConfig.lib['crate-type'].push('cdylib');
139 yield fs.writeFile(path.join(cargoDir, 'Cargo.toml'), toml.stringify(cargoConfig));
140 } // Run cargo
141
142
143 let args = ['+nightly', 'build', '--target', RUST_TARGET, '--release'];
144 yield exec('cargo', args, {
145 cwd: cargoDir
146 }); // Get output file paths
147
148 let _ref5 = yield exec('cargo', ['metadata', '--format-version', '1'], {
149 cwd: cargoDir
150 }),
151 _ref6 = (0, _slicedToArray2.default)(_ref5, 1),
152 stdout = _ref6[0];
153
154 const cargoMetadata = JSON.parse(stdout);
155 const cargoTargetDir = cargoMetadata.target_directory;
156 let outDir = path.join(cargoTargetDir, RUST_TARGET, 'release'); // Rust converts '-' to '_' when outputting files.
157
158 let rustName = cargoConfig.package.name.replace(/-/g, '_');
159 _this2.wasmPath = path.join(outDir, rustName + '.wasm');
160 _this2.depsPath = path.join(outDir, rustName + '.d');
161 })();
162 }
163
164 rustcBuild() {
165 var _this3 = this;
166
167 return (0, _asyncToGenerator2.default)(function* () {
168 // Get output filename
169 yield fs.mkdirp(_this3.options.cacheDir);
170 let name = md5(_this3.name);
171 _this3.wasmPath = path.join(_this3.options.cacheDir, name + '.wasm'); // Run rustc to compile the code
172
173 const args = ['+nightly', '--target', RUST_TARGET, '-O', '--crate-type=cdylib', _this3.name, '-o', _this3.wasmPath];
174 yield exec('rustc', args); // Run again to collect dependencies
175
176 _this3.depsPath = path.join(_this3.options.cacheDir, name + '.d');
177 yield exec('rustc', [_this3.name, '--emit=dep-info', '-o', _this3.depsPath]);
178 })();
179 }
180
181 collectDependencies() {
182 var _this4 = this;
183
184 return (0, _asyncToGenerator2.default)(function* () {
185 // Read deps file
186 let contents = yield fs.readFile(_this4.depsPath, 'utf8');
187 let dir = path.dirname(_this4.name);
188 let deps = contents.split('\n').filter(Boolean).slice(1);
189 var _iteratorNormalCompletion = true;
190 var _didIteratorError = false;
191 var _iteratorError = undefined;
192
193 try {
194 for (var _iterator = deps[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
195 let dep = _step.value;
196 dep = path.resolve(dir, dep.slice(0, dep.indexOf(': ')));
197
198 if (dep !== _this4.name) {
199 _this4.addDependency(dep, {
200 includedInParent: true
201 });
202 }
203 }
204 } catch (err) {
205 _didIteratorError = true;
206 _iteratorError = err;
207 } finally {
208 try {
209 if (!_iteratorNormalCompletion && _iterator.return != null) {
210 _iterator.return();
211 }
212 } finally {
213 if (_didIteratorError) {
214 throw _iteratorError;
215 }
216 }
217 }
218 })();
219 }
220
221 generate() {
222 var _this5 = this;
223
224 return (0, _asyncToGenerator2.default)(function* () {
225 return {
226 wasm: {
227 path: _this5.wasmPath,
228 // pass output path to RawPackager
229 mtime: Date.now() // force re-bundling since otherwise the hash would never change
230
231 }
232 };
233 })();
234 }
235
236}
237
238module.exports = RustAsset;
\No newline at end of file