• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 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 JSON5 = require('json5');
17const path = require('path');
18const fs = require('fs');
19
20class Project {
21  constructor(projectPath, nonProject) {
22    this.projectPath = projectPath;
23    this.nonProject = nonProject;
24    this.logTag = 'Project';
25  }
26
27  getPath() {
28    return this.projectPath;
29  }
30
31  getProfile() {
32    if (!this.profile) {
33      const buildProfilePath = path.resolve(this.projectPath, 'build-profile.json5');
34      if (!fs.existsSync(buildProfilePath)) {
35        Logger.error(this.logTag, 'build-profile.json5 can\'t be found, is it an openharmony project?');
36        return this.profile;
37      }
38      const profileContent = fs.readFileSync(buildProfilePath, 'utf-8');
39      try {
40        this.profile = JSON5.parse(profileContent);
41      } catch (ex) {
42        Logger.error(this.logTag, `parse build-profile.json error: ${JSON.stringify(ex)}`);
43      }
44    }
45    return this.profile;
46  }
47
48  getAppSdkVersion() {
49    const profile = this.getProfile();
50    if (profile && profile.app && profile.app.compileSdkVersion) {
51      return profile.app.compileSdkVersion;
52    }
53    return undefined;
54  }
55
56  getAppSdkPath() {
57    if (this.sdkPath) {
58      return this.sdkPath;
59    }
60    const localPropertiesPath = path.resolve(this.projectPath, 'local.properties');
61    if (!fs.existsSync(localPropertiesPath)) {
62      Logger.error(this.logTag, 'unable to get the sdk path of the project, specify it using the --sdk or --sdkRoot');
63      return this.sdkPath;
64    }
65    const properties = this.parseProperty(localPropertiesPath);
66    this.sdkPath = properties.get('sdk.dir');
67    return this.sdkPath;
68  }
69
70  parseProperty(propertyFilePath) {
71    const properties = fs.readFileSync(propertyFilePath, 'utf-8');
72    const lines = properties.split('\n');
73    const propertyRegExp = new RegExp(/(.*)=(.*)/);
74    const map = new Map();
75    lines.forEach((line) => {
76      if (line.startsWith('#')) {
77        return;
78      }
79      const expArray = line.match(propertyRegExp);
80      if (expArray && expArray.length === 3) {
81        map.set(expArray[1].trim(), expArray[2].trim());
82      }
83    });
84    return map;
85  }
86
87  /**
88   * 获取应用的源码列表
89   *
90   * @returns
91   */
92  getAppSources(isIncludeTest) {
93    if (this.nonProject) {
94      return this.getNonProjectAppSources();
95    }
96    const profile = this.getProfile();
97    if (!profile || !profile.modules || profile.modules.length === 0) {
98      return new Set();
99    }
100    const moduleSrcPaths = [];
101    profile.modules.forEach((module) => {
102      if (module.srcPath) {
103        moduleSrcPaths.push(path.resolve(this.projectPath, module.srcPath));
104      }
105    });
106    const appSources = [];
107    moduleSrcPaths.forEach((moduleSrc) => {
108      appSources.push(...this.getModuleSource(moduleSrc, isIncludeTest));
109    });
110    return new Set(appSources);
111  }
112
113  getNonProjectAppSources() {
114    Logger.info(this.logTag, 'find source files in non-project');
115    const appSources = [];
116    this.listSourceFiles(this.projectPath, appSources);
117    return new Set(appSources);
118  }
119
120  getModuleSource(modulePath, isIncludeTest) {
121    const sourceSets = ['src/main/ets'];
122    if (isIncludeTest) {
123      sourceSets.push(...['src/ohosTest/ets']);
124    }
125    const sources = [];
126    sourceSets.forEach((sourcePath) => {
127      const srcPath = path.resolve(modulePath, sourcePath);
128      this.listSourceFiles(srcPath, sources);
129    });
130    if (sources.length === 0) {
131      Logger.info(this.logTag, `can't find source file in ${this.projectPath}`);
132    }
133    return sources;
134  }
135
136  listSourceFiles(srcPath, dest) {
137    if (fs.existsSync(srcPath)) {
138      Logger.info(this.logTag, `find source code in ${srcPath}`);
139      FileSystem.listFiles(srcPath, (filePath) => {
140        const fileName = path.basename(filePath);
141        return fileName.endsWith('.ts') || fileName.endsWith('.ets');
142      }, dest);
143    }
144  }
145}
146
147class Sdk {
148
149  /**
150   *
151   * @param {Project} project 应用工程对象
152   * @param {string} sdkEtsPath 指向sdk中ets目录的路径
153   * @param {string} sdkRoot sdk根目录
154   */
155  constructor(project, sdkEtsPath, sdkRoot) {
156    this.project = project;
157    this.sdkEtsPath = sdkEtsPath;
158    this.sdkRoot = sdkRoot;
159  }
160
161  getPath() {
162    if (this.sdkEtsPath) {
163      return this.sdkEtsPath;
164    }
165    if (this.sdkApiRoot) {
166      return this.sdkApiRoot;
167    }
168    const sdkVersion = this.project.getAppSdkVersion();
169    const sdkDir = this.sdkRoot || this.project.getAppSdkPath();
170    if (sdkVersion && sdkDir) {
171      this.sdkApiRoot = path.resolve(sdkDir, `${sdkVersion}`, 'ets');
172    }
173    return this.sdkApiRoot;
174  }
175
176  /**
177   * 获取SDK的d.ts文件列表
178   *
179   * @param {string} sdkRoot
180   * @returns
181   */
182  getApiLibs() {
183    if (this.apiLibs) {
184      return this.apiLibs;
185    }
186    this.apiLibs = [];
187    this.listDtsFiles('api', this.apiLibs);
188    return this.apiLibs;
189  }
190
191  getComponentLibs() {
192    if (this.componentLibs) {
193      return this.componentLibs;
194    }
195    this.componentLibs = [];
196    this.listDtsFiles('component', this.componentLibs);
197    return this.componentLibs;
198  }
199
200  getESLibs(libPath) {
201    if (!process.env.bundleMode) {
202      return [];
203    }
204    Logger.info('Sdk', `find ES libs in ${libPath}`);
205    if (this.esLibs) {
206      return this.esLibs;
207    }
208    this.esLibs = [];
209    FileSystem.listFiles(libPath, (filePath) => path.basename(filePath).endsWith('.d.ts'), this.esLibs);
210    return this.esLibs;
211  }
212
213  listDtsFiles(dir, dest) {
214    const sdkRoot = this.getPath();
215    if (!sdkRoot) {
216      return new Set();
217    }
218    const subDir = path.resolve(sdkRoot, dir);
219    FileSystem.listFiles(subDir, (filePath) => path.basename(filePath).endsWith('.d.ts'), dest);
220  }
221}
222
223class FileSystem {
224  static listFiles(dir, filter, dest) {
225    const files = fs.readdirSync(dir);
226    files.forEach((element) => {
227      const filePath = path.join(dir, element);
228      const status = fs.statSync(filePath);
229      if (status.isDirectory()) {
230        this.listFiles(filePath, filter, dest);
231      } else if (filter(filePath)) {
232        dest.push(this.convertToPosixPath(filePath));
233      }
234    });
235  }
236
237  static convertToPosixPath(filePath) {
238    return filePath.split(path.sep).join(path.posix.sep);
239  }
240
241  static isInDirectory(parentDir, subPath) {
242    const relative = path.relative(parentDir, subPath);
243    return (relative === '' || !relative.startsWith('..')) && !path.isAbsolute(relative);
244  }
245
246  static listAllAppDirs(parentDir) {
247    const dest = [];
248    this.listDirectory(parentDir, dest, (filePath) => {
249      const buildProfilePath = path.resolve(filePath, 'build-profile.json5');
250      if (!fs.existsSync(buildProfilePath)) {
251        return false;
252      }
253      const profileContent = fs.readFileSync(buildProfilePath, 'utf-8');
254      const profile = JSON5.parse(profileContent);
255      return profile.app && profile.modules;
256    }, (filePath) => {
257      return filePath;
258    });
259    return dest;
260  }
261
262  static listDirectory(dir, dest, filter, visitChildren) {
263    const files = fs.readdirSync(dir);
264    files.forEach((element) => {
265      const filePath = path.join(dir, element);
266      const status = fs.statSync(filePath);
267      if (status.isDirectory()) {
268        if (filter(filePath)) {
269          dest.push(filePath);
270        } else if (visitChildren(filePath)) {
271          this.listDirectory(filePath, dest, filter, visitChildren);
272        }
273      }
274    });
275  }
276}
277
278class Logger {
279  static INFO = 0;
280  static WARN = 1;
281  static ERROR = 2;
282  static logs = '';
283  static LEVEL_NAME = new Map([
284    [this.INFO, 'I'],
285    [this.WARN, 'W'],
286    [this.ERROR, 'E']
287  ]);
288
289  static info(tag, message) {
290    this.wrap(this.INFO, tag, message);
291  }
292
293  static warn(tag, message) {
294    this.wrap(this.WARN, tag, message);
295  }
296
297  static error(tag, message) {
298    this.wrap(this.ERROR, tag, message);
299  }
300
301  static wrap(level, tag, message) {
302    const timeStamp = `${this.formatDate(Date.now(), 'Y-M-D H:m:s:x')}`;
303    const logMessage = `${timeStamp} ${this.getLevelName(level)} [${tag}] ${message}`;
304    console.log(logMessage);
305  }
306
307  static flush(output) {
308    const logName = path.resolve(output, `${this.formatDate(Date.now(), 'Y-M-D-Hmsx')}.log`);
309    fs.writeFileSync(logName, this.logs);
310    this.info('Logger', `log is in ${logName}`);
311  }
312
313  static getLevelName(level) {
314    if (this.LEVEL_NAME.has(level)) {
315      return this.LEVEL_NAME.get(level);
316    }
317    return this.LEVEL_NAME.get(this.INFO);
318  }
319
320  static formatDate(time, format) {
321    const date = new Date(time);
322    const year = date.getFullYear();
323    const month = date.getMonth() + 1;
324    const day = date.getDate();
325    const hour = date.getHours();
326    const min = date.getMinutes();
327    const sec = date.getSeconds();
328    const mis = date.getMilliseconds();
329    let dateStr = format.replace('Y', `${year}`);
330    dateStr = dateStr.replace('M', `${month}`);
331    dateStr = dateStr.replace('D', `${day}`);
332    dateStr = dateStr.replace('H', `${hour}`);
333    dateStr = dateStr.replace('m', `${min}`);
334    dateStr = dateStr.replace('s', `${sec}`);
335    dateStr = dateStr.replace('x', `${mis}`);
336    return dateStr;
337  }
338}
339
340exports.Project = Project;
341exports.Sdk = Sdk;
342exports.FileSystem = FileSystem;
343exports.Logger = Logger;