• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2020 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16const fs = require('fs');
17const path = require('path');
18const md5 = require('md5');
19
20const { readFile } = require('./lib/utils');
21const { WORKERS_DIR } = require('./lib/pre_define');
22
23const {
24  configure,
25  getLogger
26} = require('log4js');
27
28configure({
29  appenders: { 'ETS': {type: 'stderr', layout: {type: 'messagePassThrough'}}},
30  categories: {'default': {appenders: ['ETS'], level: 'info'}}
31});
32const logger = getLogger('ETS');
33
34const staticPreviewPage = process.env.aceStaticPreview;
35const aceCompileMode = process.env.aceCompileMode || 'page';
36const abilityConfig = {
37  abilityType: process.env.abilityType || 'page',
38  abilityEntryFile: null,
39  projectAbilityPath: [],
40  testRunnerFile: []
41};
42const projectConfig = {};
43const resources = {
44  app: {},
45  sys: {}
46};
47
48const systemModules = [];
49
50function initProjectConfig(projectConfig) {
51  projectConfig.entryObj = {};
52  projectConfig.projectPath = projectConfig.projectPath || process.env.aceModuleRoot ||
53    path.join(process.cwd(), 'sample');
54  projectConfig.buildPath = projectConfig.buildPath || process.env.aceModuleBuild ||
55    path.resolve(projectConfig.projectPath, 'build');
56  projectConfig.manifestFilePath = projectConfig.manifestFilePath || process.env.aceManifestPath ||
57    path.join(projectConfig.projectPath, 'manifest.json');
58  projectConfig.aceProfilePath = projectConfig.aceProfilePath || process.env.aceProfilePath;
59  projectConfig.aceModuleJsonPath = projectConfig.aceModuleJsonPath || process.env.aceModuleJsonPath;
60  projectConfig.aceSuperVisualPath = projectConfig.aceSuperVisualPath ||
61    process.env.aceSuperVisualPath;
62  projectConfig.hashProjectPath = projectConfig.hashProjectPath ||
63    hashProjectPath(projectConfig.projectPath);
64  projectConfig.aceBuildJson = projectConfig.aceBuildJson || process.env.aceBuildJson;
65  projectConfig.cachePath = projectConfig.cachePath || process.env.cachePath ||
66    path.resolve(__dirname, 'node_modules/.cache');
67  projectConfig.aceSoPath = projectConfig.aceSoPath || process.env.aceSoPath;
68  projectConfig.xtsMode = /ets_loader_ark$/.test(__dirname);
69  projectConfig.localPropertiesPath = projectConfig.localPropertiesPath || process.env.localPropertiesPath;
70  projectConfig.projectProfilePath = projectConfig.projectProfilePath || process.env.projectProfilePath;
71  projectConfig.compileMode = projectConfig.compileMode || 'jsbundle';
72}
73
74function loadEntryObj(projectConfig) {
75  let manifest = {};
76  initProjectConfig(projectConfig);
77  if (process.env.aceManifestPath && aceCompileMode === 'page') {
78    setEntryFile(projectConfig);
79    setFaTestRunnerFile(projectConfig);
80  }
81  if (process.env.aceModuleJsonPath) {
82    setAbilityPages(projectConfig);
83    setStageTestRunnerFile(projectConfig);
84  }
85
86  if(staticPreviewPage) {
87    projectConfig.entryObj['./' + staticPreviewPage] = projectConfig.projectPath + path.sep +
88      staticPreviewPage + '.ets?entry';
89  } else if (abilityConfig.abilityType === 'page') {
90    if (fs.existsSync(projectConfig.manifestFilePath)) {
91      const jsonString = fs.readFileSync(projectConfig.manifestFilePath).toString();
92      manifest = JSON.parse(jsonString);
93      projectConfig.pagesJsonFileName = 'config.json';
94    } else if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) {
95      process.env.compileMode = 'moduleJson';
96      buildManifest(manifest, projectConfig.aceModuleJsonPath);
97    } else {
98      throw Error('\u001b[31m ERROR: the manifest file ' + projectConfig.manifestFilePath.replace(/\\/g, '/') +
99        ' or module.json is lost or format is invalid. \u001b[39m').message;
100    }
101    if (manifest.pages) {
102      const pages = manifest.pages;
103      pages.forEach((element) => {
104        const sourcePath = element.replace(/^\.\/ets\//, '');
105        const fileName = path.resolve(projectConfig.projectPath, sourcePath + '.ets');
106        if (fs.existsSync(fileName)) {
107          projectConfig.entryObj['./' + sourcePath] = fileName + '?entry';
108        } else {
109          throw Error(`\u001b[31m ERROR: page '${fileName.replace(/\\/g, '/')}' does not exist. \u001b[39m`)
110            .message;
111        }
112      });
113    } else {
114      throw Error('\u001b[31m ERROR: missing pages attribute in ' +
115        projectConfig.manifestFilePath.replace(/\\/g, '/') +
116        '. \u001b[39m').message;
117    }
118  }
119}
120
121function buildManifest(manifest, aceConfigPath) {
122  try {
123    const moduleConfigJson = JSON.parse(fs.readFileSync(aceConfigPath).toString());
124    manifest.type = process.env.abilityType;
125    if (moduleConfigJson.module && moduleConfigJson.module.uiSyntax === 'ets') {
126      manifest.pages = getPages(moduleConfigJson);
127    } else {
128      throw Error('\u001b[31m'+
129        'ERROR: the config.json file miss key word module || module[abilities].' +
130        '\u001b[39m').message;
131    }
132  } catch (e) {
133    throw Error("\x1B[31m" + 'ERROR: the module.json file is lost or format is invalid.' +
134      "\x1B[39m").message;
135  }
136}
137
138function getPages(configJson) {
139  const pages = []
140  const pagesJsonFileName = `${configJson.module.pages.replace(/\$profile\:/, '')}.json`;
141  const modulePagePath = path.resolve(projectConfig.aceProfilePath, pagesJsonFileName);
142  if (fs.existsSync(modulePagePath)) {
143    const pagesConfig = JSON.parse(fs.readFileSync(modulePagePath, 'utf-8'));
144    if (pagesConfig && pagesConfig.src) {
145      projectConfig.pagesJsonFileName = pagesJsonFileName;
146      return pagesConfig.src;
147    }
148  }
149  return pages;
150}
151
152function setEntryFile(projectConfig) {
153  const entryFileName = abilityConfig.abilityType === 'page' ? 'app' : abilityConfig.abilityType;
154  const extendFile = entryFileName === 'app' ? '.ets' : '.ts';
155  const entryFileRealPath = entryFileName + extendFile;
156  const entryFilePath = path.resolve(projectConfig.projectPath, entryFileRealPath);
157  abilityConfig.abilityEntryFile = entryFilePath;
158  if (!fs.existsSync(entryFilePath) && aceCompileMode === 'page') {
159    throw Error(`\u001b[31m ERROR: missing ${entryFilePath.replace(/\\/g, '/')}. \u001b[39m`).message;
160  }
161  projectConfig.entryObj[`./${entryFileName}`] = entryFilePath + '?entry';
162}
163
164function setAbilityPages(projectConfig) {
165  let abilityPages = [];
166  if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) {
167    const moduleJson = JSON.parse(fs.readFileSync(projectConfig.aceModuleJsonPath).toString());
168    abilityPages = readAbilityEntrance(moduleJson);
169    setAbilityFile(projectConfig, abilityPages);
170  }
171}
172
173function setFaTestRunnerFile(projectConfig) {
174  const index =projectConfig.projectPath.split(path.sep).join('/').lastIndexOf('\/');
175  const testRunnerPath = path.resolve(projectConfig.projectPath.substring(0,index + 1), "TestRunner");
176  if (fs.existsSync(testRunnerPath)) {
177    const testRunnerFiles = [];
178    readFile(testRunnerPath, testRunnerFiles);
179    testRunnerFiles.forEach((item) => {
180      if (/\.(ts|js)$/.test(item)) {
181        const relativePath = path.relative(testRunnerPath, item).replace(/\.(ts|js)$/, '');
182        projectConfig.entryObj["../TestRunner/" + relativePath] = item;
183        abilityConfig.testRunnerFile.push(item);
184      }
185    });
186  }
187}
188
189function setStageTestRunnerFile(projectConfig) {
190  const index = projectConfig.projectPath.split(path.sep).join('/').lastIndexOf('\/');
191  const testRunnerPath = path.resolve(projectConfig.projectPath, "TestRunner");
192  if (fs.existsSync(testRunnerPath)) {
193    const testRunnerFiles = [];
194    readFile(testRunnerPath, testRunnerFiles);
195    testRunnerFiles.forEach((item) => {
196      if (/\.(ts|js)$/.test(item)) {
197        const relativePath = path.relative(testRunnerPath, item).replace(/\.(ts|js)$/, '');
198		    projectConfig.entryObj["./TestRunner/" + relativePath] = item;
199        abilityConfig.testRunnerFile.push(item);
200      }
201    });
202  }
203}
204
205function setAbilityFile(projectConfig, abilityPages) {
206  abilityPages.forEach(abilityPath => {
207    const projectAbilityPath = path.resolve(projectConfig.projectPath, '../', abilityPath);
208    const entryPageKey = abilityPath.replace(/^\.\/ets\//, './').replace(/\.ts$/, '');
209    if (fs.existsSync(projectAbilityPath)) {
210      abilityConfig.projectAbilityPath.push(projectAbilityPath);
211      projectConfig.entryObj[entryPageKey] = projectAbilityPath + '?entry';
212    } else {
213      throw Error(
214        `\u001b[31m ERROR: srcEntrance file '${projectAbilityPath.replace(/\\/g, '/')}' does not exist. \u001b[39m`
215      ).message;
216    }
217  });
218}
219
220function readAbilityEntrance(moduleJson) {
221  let abilityPages = [];
222  if (moduleJson.module) {
223    if (moduleJson.module.srcEntrance) {
224      abilityPages.push(moduleJson.module.srcEntrance);
225    }
226    if (moduleJson.module.abilities && moduleJson.module.abilities.length > 0) {
227      setEntrance(moduleJson.module.abilities, abilityPages);
228    }
229    if (moduleJson.module.extensionAbilities && moduleJson.module.extensionAbilities.length > 0) {
230      setEntrance(moduleJson.module.extensionAbilities, abilityPages);
231    }
232  }
233  return abilityPages;
234}
235
236function setEntrance(abilityConfig, abilityPages) {
237  if (abilityConfig && abilityConfig.length > 0) {
238    abilityConfig.forEach(ability => {
239      if (ability.srcEntrance) {
240        abilityPages.push(ability.srcEntrance);
241      }
242    });
243  }
244}
245
246function loadWorker(projectConfig, workerFileEntry) {
247  if (workerFileEntry) {
248    projectConfig.entryObj = Object.assign(projectConfig.entryObj, workerFileEntry);
249  } else {
250    const workerPath = path.resolve(projectConfig.projectPath, WORKERS_DIR);
251    if (fs.existsSync(workerPath)) {
252      const workerFiles = [];
253      readFile(workerPath, workerFiles);
254      workerFiles.forEach((item) => {
255        if (/\.(ts|js)$/.test(item)) {
256          const relativePath = path.relative(workerPath, item)
257            .replace(/\.(ts|js)$/, '').replace(/\\/g, '/');
258          projectConfig.entryObj[`./${WORKERS_DIR}/` + relativePath] = item;
259        }
260      })
261    }
262  }
263}
264
265function readWorkerFile() {
266  const workerFileEntry = {};
267  if (projectConfig.aceBuildJson && fs.existsSync(projectConfig.aceBuildJson)) {
268    const workerConfig = JSON.parse(fs.readFileSync(projectConfig.aceBuildJson).toString());
269    if (workerConfig.workers) {
270      workerConfig.workers.forEach(worker => {
271        if (!/\.(ts|js)$/.test(worker)) {
272          worker += '.ts';
273        }
274        const relativePath = path.relative(projectConfig.projectPath, worker);
275        if (filterWorker(relativePath)) {
276          const workerKey = relativePath.replace(/\.(ts|js)$/, '').replace(/\\/g, '/');
277          if (workerFileEntry[workerKey]) {
278            throw Error(
279              '\u001b[31m ERROR: The worker file cannot use the same file name: \n' +
280              workerFileEntry[workerKey] + '\n' + worker + '\u001b[39m'
281            ).message;
282          } else {
283            workerFileEntry[workerKey] = worker;
284          }
285        }
286      });
287      return workerFileEntry;
288    }
289  }
290  return null;
291}
292
293function filterWorker(workerPath) {
294  return /\.(ts|js)$/.test(workerPath);
295}
296
297;(function initSystemResource() {
298  const sysResourcePath = path.resolve('./sysResource.js');
299  if (fs.existsSync(sysResourcePath)) {
300    resources.sys = require(sysResourcePath).sys;
301  }
302})()
303
304;(function readSystemModules() {
305  const systemModulesPath = path.resolve(__dirname,'../../api');
306  if (fs.existsSync(systemModulesPath)) {
307    systemModules.push(...fs.readdirSync(systemModulesPath));
308  }
309})()
310
311function readAppResource(resources, filePath) {
312  if (fs.existsSync(filePath)) {
313    const appResource = fs.readFileSync(filePath, "utf-8");
314    const resourceArr = appResource.split(/\n/);
315    let resourceMap = new Map();
316    processResourceArr(resourceArr, resourceMap, filePath);
317    for (let [key, value] of resourceMap) {
318      resources.app[key] = value;
319    }
320  }
321}
322
323function processResourceArr(resourceArr, resourceMap, filePath) {
324  for (let i = 0; i < resourceArr.length; i++) {
325    if (!resourceArr[i].length) {
326      continue;
327    }
328    const resourceData = resourceArr[i].split(/\s/);
329    if (resourceData.length === 3 && !isNaN(Number(resourceData[2])) ) {
330      if (resourceMap.get(resourceData[0])) {
331        const resourceKeys = resourceMap.get(resourceData[0]);
332        if (!resourceKeys[resourceData[1]] || resourceKeys[resourceData[1]] !== Number(resourceData[2])) {
333          resourceKeys[resourceData[1]] = Number(resourceData[2]);
334        }
335      } else {
336        let obj = {};
337        obj[resourceData[1]] = Number(resourceData[2]);
338        resourceMap.set(resourceData[0], obj);
339      }
340    } else {
341      logger.warn(`\u001b[31m ArkTS:WARN The format of file '${filePath}' is incorrect. \u001b[39m`);
342      break;
343    }
344  }
345}
346
347function hashProjectPath(projectPath) {
348  process.env.hashProjectPath = "_" + md5(projectPath);
349  return process.env.hashProjectPath;
350}
351
352function checkAppResourcePath(appResourcePath, config) {
353  if (appResourcePath) {
354    readAppResource(resources, appResourcePath);
355    if (fs.existsSync(appResourcePath) && config.cache) {
356      config.cache.buildDependencies.config.push(appResourcePath);
357    }
358    if (!projectConfig.xtsMode) {
359      const appResourcePathSavePath = path.resolve(projectConfig.cachePath, 'resource_path.txt');
360      saveAppResourcePath(appResourcePath, appResourcePathSavePath);
361      if (fs.existsSync(appResourcePathSavePath) && config.cache) {
362        config.cache.buildDependencies.config.push(appResourcePathSavePath);
363      }
364    }
365  }
366}
367
368function saveAppResourcePath(appResourcePath, appResourcePathSavePath) {
369  let isSave = false;
370  if (fs.existsSync(appResourcePathSavePath)) {
371    const saveContent = fs.readFileSync(appResourcePathSavePath);
372    if (appResourcePath !== saveContent) {
373      isSave = true;
374    }
375  } else {
376    isSave = true;
377  }
378  if (isSave) {
379    fs.writeFileSync(appResourcePathSavePath, appResourcePath);
380  }
381}
382
383function addSDKBuildDependencies(config) {
384  if (projectConfig.localPropertiesPath &&
385    fs.existsSync(projectConfig.localPropertiesPath) && config.cache) {
386    config.cache.buildDependencies.config.push(projectConfig.localPropertiesPath)
387  }
388  if (projectConfig.projectProfilePath &&
389    fs.existsSync(projectConfig.projectProfilePath) && config.cache) {
390    config.cache.buildDependencies.config.push(projectConfig.projectProfilePath)
391  }
392}
393
394const globalProgram = {
395  program: null,
396  watchProgram: null
397};
398
399exports.globalProgram = globalProgram;
400exports.projectConfig = projectConfig;
401exports.loadEntryObj = loadEntryObj;
402exports.readAppResource = readAppResource;
403exports.resources = resources;
404exports.loadWorker = loadWorker;
405exports.abilityConfig = abilityConfig;
406exports.readWorkerFile = readWorkerFile;
407exports.systemModules = systemModules;
408exports.checkAppResourcePath = checkAppResourcePath;
409exports.addSDKBuildDependencies = addSDKBuildDependencies;
410