1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | 'use strict';
|
20 |
|
21 | const fs = require('fs-extra');
|
22 | const path = require('path');
|
23 | const util = require('util');
|
24 | const execa = require('execa');
|
25 | const { CordovaError, events } = require('cordova-common');
|
26 |
|
27 | Podfile.FILENAME = 'Podfile';
|
28 | Podfile.declarationRegexpMap = {
|
29 | 'use_frameworks!': 'use[-_]frameworks!?',
|
30 | 'inhibit_all_warnings!': 'inhibit[-_]all[-_]warnings!?'
|
31 | };
|
32 |
|
33 | function Podfile (podFilePath, projectName, minDeploymentTarget) {
|
34 | this.declarationToken = '##INSERT_DECLARATION##';
|
35 | this.sourceToken = '##INSERT_SOURCE##';
|
36 | this.podToken = '##INSERT_POD##';
|
37 |
|
38 | this.path = podFilePath;
|
39 | this.projectName = projectName;
|
40 | this.minDeploymentTarget = minDeploymentTarget || '11.0';
|
41 | this.contents = null;
|
42 | this.sources = null;
|
43 | this.declarations = null;
|
44 | this.pods = null;
|
45 | this.__dirty = false;
|
46 |
|
47 |
|
48 | const filename = this.path.split(path.sep).pop();
|
49 | if (filename !== Podfile.FILENAME) {
|
50 | throw new CordovaError(util.format('Podfile: The file at %s is not `%s`.', this.path, Podfile.FILENAME));
|
51 | }
|
52 |
|
53 | if (!projectName) {
|
54 | throw new CordovaError('Podfile: The projectName was not specified in the constructor.');
|
55 | }
|
56 |
|
57 | if (!fs.existsSync(this.path)) {
|
58 | events.emit('verbose', util.format('Podfile: The file at %s does not exist.', this.path));
|
59 | events.emit('verbose', 'Creating new Podfile in platforms/ios');
|
60 | this.clear();
|
61 | this.write();
|
62 | } else {
|
63 | events.emit('verbose', 'Podfile found in platforms/ios');
|
64 |
|
65 | const fileText = fs.readFileSync(this.path, 'utf8');
|
66 | this.declarations = this.__parseForDeclarations(fileText);
|
67 | this.sources = this.__parseForSources(fileText);
|
68 | this.pods = this.__parseForPods(fileText);
|
69 | }
|
70 | }
|
71 |
|
72 | Podfile.prototype.__parseForDeclarations = function (text) {
|
73 |
|
74 | const arr = text.split('\n');
|
75 |
|
76 |
|
77 | const declarationsPreRE = /platform :ios,\s+'[^']+'/;
|
78 | const declarationsPostRE = /target\s+'[^']+'\s+do/;
|
79 | const declarationRE = /^\s*[^#]/;
|
80 |
|
81 | return arr.reduce((acc, line) => {
|
82 | switch (acc.state) {
|
83 | case 0:
|
84 | if (declarationsPreRE.exec(line)) {
|
85 | acc.state = 1;
|
86 | }
|
87 | break;
|
88 | case 1:
|
89 | if (declarationsPostRE.exec(line)) {
|
90 | acc.state = 2;
|
91 | } else {
|
92 | acc.lines.push(line);
|
93 | }
|
94 | break;
|
95 | case 2:
|
96 | default:
|
97 |
|
98 | }
|
99 | return acc;
|
100 | }, { state: 0, lines: [] })
|
101 | .lines
|
102 | .filter(line => declarationRE.exec(line))
|
103 | .reduce((obj, line) => {
|
104 | obj[line] = line;
|
105 | return obj;
|
106 | }, {});
|
107 | };
|
108 |
|
109 | Podfile.prototype.__parseForSources = function (text) {
|
110 |
|
111 | const arr = text.split('\n');
|
112 |
|
113 | const sourceRE = /source '(.*)'/;
|
114 | return arr.filter(line => {
|
115 | const m = sourceRE.exec(line);
|
116 |
|
117 | return (m !== null);
|
118 | })
|
119 | .reduce((obj, line) => {
|
120 | const m = sourceRE.exec(line);
|
121 | if (m !== null) {
|
122 | const source = m[1];
|
123 | obj[source] = source;
|
124 | }
|
125 | return obj;
|
126 | }, {});
|
127 | };
|
128 |
|
129 | Podfile.prototype.__parseForPods = function (text) {
|
130 |
|
131 | const arr = text.split('\n');
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | const podRE = /pod '([^']*)'\s*(?:,\s*'([^']*)'\s*)?,?\s*(.*)/;
|
139 |
|
140 |
|
141 | return arr.filter(line => {
|
142 | const m = podRE.exec(line);
|
143 |
|
144 | return (m !== null);
|
145 | })
|
146 | .reduce((obj, line) => {
|
147 | const m = podRE.exec(line);
|
148 |
|
149 | if (m !== null) {
|
150 | const podspec = {
|
151 | name: m[1]
|
152 | };
|
153 | if (m[2]) {
|
154 | podspec.spec = m[2];
|
155 | }
|
156 | if (m[3]) {
|
157 | podspec.options = m[3];
|
158 | }
|
159 | obj[m[1]] = podspec;
|
160 | }
|
161 |
|
162 | return obj;
|
163 | }, {});
|
164 | };
|
165 |
|
166 | Podfile.prototype.escapeSingleQuotes = function (string) {
|
167 | return string.replace(/'/g, '\\\'');
|
168 | };
|
169 |
|
170 | Podfile.prototype.getTemplate = function () {
|
171 |
|
172 | const projectName = this.escapeSingleQuotes(this.projectName);
|
173 | return util.format(
|
174 | '# DO NOT MODIFY -- auto-generated by Apache Cordova\n' +
|
175 | '%s\n' +
|
176 | 'platform :ios, \'%s\'\n' +
|
177 | '%s\n' +
|
178 | 'target \'%s\' do\n' +
|
179 | '\tproject \'%s.xcodeproj\'\n' +
|
180 | '%s\n' +
|
181 | 'end\n',
|
182 | this.sourceToken, this.minDeploymentTarget, this.declarationToken, projectName, projectName, this.podToken);
|
183 | };
|
184 |
|
185 | Podfile.prototype.addSpec = function (name, spec) {
|
186 | name = name || '';
|
187 |
|
188 | spec = spec;
|
189 |
|
190 | if (!name.length) {
|
191 | throw new CordovaError('Podfile addSpec: name is not specified.');
|
192 | }
|
193 |
|
194 | if (typeof spec === 'string') {
|
195 | if (spec.startsWith(':')) {
|
196 | spec = { name, options: spec };
|
197 | } else {
|
198 | spec = { name, spec };
|
199 | }
|
200 | }
|
201 |
|
202 | this.pods[name] = spec;
|
203 | this.__dirty = true;
|
204 |
|
205 | events.emit('verbose', util.format('Added pod line for `%s`', name));
|
206 | };
|
207 |
|
208 | Podfile.prototype.removeSpec = function (name) {
|
209 | if (this.existsSpec(name)) {
|
210 | delete this.pods[name];
|
211 | this.__dirty = true;
|
212 | }
|
213 |
|
214 | events.emit('verbose', util.format('Removed pod line for `%s`', name));
|
215 | };
|
216 |
|
217 | Podfile.prototype.getSpec = function (name) {
|
218 | return this.pods[name];
|
219 | };
|
220 |
|
221 | Podfile.prototype.existsSpec = function (name) {
|
222 | return (name in this.pods);
|
223 | };
|
224 |
|
225 | Podfile.prototype.addSource = function (src) {
|
226 | this.sources[src] = src;
|
227 | this.__dirty = true;
|
228 |
|
229 | events.emit('verbose', util.format('Added source line for `%s`', src));
|
230 | };
|
231 |
|
232 | Podfile.prototype.removeSource = function (src) {
|
233 | if (this.existsSource(src)) {
|
234 | delete this.sources[src];
|
235 | this.__dirty = true;
|
236 | }
|
237 |
|
238 | events.emit('verbose', util.format('Removed source line for `%s`', src));
|
239 | };
|
240 |
|
241 | Podfile.prototype.existsSource = function (src) {
|
242 | return (src in this.sources);
|
243 | };
|
244 |
|
245 | Podfile.prototype.addDeclaration = function (declaration) {
|
246 | this.declarations[declaration] = declaration;
|
247 | this.__dirty = true;
|
248 |
|
249 | events.emit('verbose', util.format('Added declaration line for `%s`', declaration));
|
250 | };
|
251 |
|
252 | Podfile.prototype.removeDeclaration = function (declaration) {
|
253 | if (this.existsDeclaration(declaration)) {
|
254 | delete this.declarations[declaration];
|
255 | this.__dirty = true;
|
256 | }
|
257 |
|
258 | events.emit('verbose', util.format('Removed source line for `%s`', declaration));
|
259 | };
|
260 |
|
261 | Podfile.proofDeclaration = declaration => {
|
262 | const list = Object.keys(Podfile.declarationRegexpMap).filter(key => {
|
263 | const regexp = new RegExp(Podfile.declarationRegexpMap[key]);
|
264 | return regexp.test(declaration);
|
265 | });
|
266 | if (list.length > 0) {
|
267 | return list[0];
|
268 | }
|
269 | return declaration;
|
270 | };
|
271 |
|
272 | Podfile.prototype.existsDeclaration = function (declaration) {
|
273 | return (declaration in this.declarations);
|
274 | };
|
275 |
|
276 | Podfile.prototype.clear = function () {
|
277 | this.sources = {};
|
278 | this.declarations = {};
|
279 | this.pods = {};
|
280 | this.__dirty = true;
|
281 | };
|
282 |
|
283 | Podfile.prototype.destroy = function () {
|
284 | fs.unlinkSync(this.path);
|
285 | events.emit('verbose', util.format('Deleted `%s`', this.path));
|
286 | };
|
287 |
|
288 | Podfile.prototype.write = function () {
|
289 | let text = this.getTemplate();
|
290 |
|
291 | const podsString =
|
292 | Object.keys(this.pods).map(key => {
|
293 | const name = key;
|
294 | const json = this.pods[key];
|
295 |
|
296 | if (typeof json === 'string') {
|
297 | const spec = json;
|
298 | if (spec.length) {
|
299 | if (spec.indexOf(':') === 0) {
|
300 |
|
301 | return util.format('\tpod \'%s\', %s', name, spec);
|
302 | } else {
|
303 |
|
304 | return util.format('\tpod \'%s\', \'%s\'', name, spec);
|
305 | }
|
306 | } else {
|
307 | return util.format('\tpod \'%s\'', name);
|
308 | }
|
309 | } else {
|
310 | const list = [`'${name}'`];
|
311 | if ('spec' in json && json.spec.length) {
|
312 | list.push(`'${json.spec}'`);
|
313 | }
|
314 |
|
315 | let options = ['tag', 'branch', 'commit', 'git', 'podspec']
|
316 | .filter(tag => tag in json)
|
317 | .map(tag => `:${tag} => '${json[tag]}'`);
|
318 |
|
319 | if ('configurations' in json) {
|
320 | options.push(`:configurations => [${json.configurations.split(',').map(conf => `'${conf.trim()}'`).join(',')}]`);
|
321 | }
|
322 | if ('options' in json) {
|
323 | options = [json.options];
|
324 | }
|
325 | if (options.length > 0) {
|
326 | list.push(options.join(', '));
|
327 | }
|
328 | return util.format('\tpod %s', list.join(', '));
|
329 | }
|
330 | }).join('\n');
|
331 |
|
332 | const sourcesString =
|
333 | Object.keys(this.sources).map(key => {
|
334 | const source = this.sources[key];
|
335 | return util.format('source \'%s\'', source);
|
336 | }).join('\n');
|
337 |
|
338 | const declarationString =
|
339 | Object.keys(this.declarations).map(key => {
|
340 | const declaration = this.declarations[key];
|
341 | return declaration;
|
342 | }).join('\n');
|
343 |
|
344 | text = text.replace(this.podToken, podsString)
|
345 | .replace(this.sourceToken, sourcesString)
|
346 | .replace(this.declarationToken, declarationString);
|
347 |
|
348 | fs.writeFileSync(this.path, text, 'utf8');
|
349 | this.__dirty = false;
|
350 |
|
351 | events.emit('verbose', 'Wrote to Podfile.');
|
352 | };
|
353 |
|
354 | Podfile.prototype.isDirty = function () {
|
355 | return this.__dirty;
|
356 | };
|
357 |
|
358 | Podfile.prototype.before_install = function (toolOptions) {
|
359 | toolOptions = toolOptions || {};
|
360 |
|
361 |
|
362 | const template =
|
363 | '// DO NOT MODIFY -- auto-generated by Apache Cordova\n' +
|
364 | '#include "Pods/Target Support Files/Pods-%s/Pods-%s.%s.xcconfig"';
|
365 |
|
366 | const debugContents = util.format(template, this.projectName, this.projectName, 'debug');
|
367 | const releaseContents = util.format(template, this.projectName, this.projectName, 'release');
|
368 |
|
369 | const debugConfigPath = path.join(this.path, '..', 'pods-debug.xcconfig');
|
370 | const releaseConfigPath = path.join(this.path, '..', 'pods-release.xcconfig');
|
371 |
|
372 | fs.writeFileSync(debugConfigPath, debugContents, 'utf8');
|
373 | fs.writeFileSync(releaseConfigPath, releaseContents, 'utf8');
|
374 |
|
375 | return Promise.resolve(toolOptions);
|
376 | };
|
377 |
|
378 | Podfile.prototype.install = function (requirementsCheckerFunction) {
|
379 | const opts = {};
|
380 | opts.cwd = path.join(this.path, '..');
|
381 | opts.stderr = 'inherit';
|
382 |
|
383 | if (!requirementsCheckerFunction) {
|
384 | requirementsCheckerFunction = Promise.resolve();
|
385 | }
|
386 |
|
387 | return requirementsCheckerFunction()
|
388 | .then(toolOptions => this.before_install(toolOptions))
|
389 | .then(toolOptions => {
|
390 | events.emit('verbose', '==== pod install start ====\n');
|
391 |
|
392 | if (toolOptions.ignore) {
|
393 | events.emit('verbose', toolOptions.ignoreMessage);
|
394 | return;
|
395 | }
|
396 |
|
397 | const subprocess = execa('pod', ['install', '--verbose'], opts);
|
398 |
|
399 |
|
400 | subprocess.stdout.on('data', data => {
|
401 | events.emit('verbose', data);
|
402 | });
|
403 |
|
404 | return subprocess;
|
405 | })
|
406 | .then(() => {
|
407 | events.emit('verbose', '==== pod install end ====\n');
|
408 | });
|
409 | };
|
410 |
|
411 | module.exports.Podfile = Podfile;
|