• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 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
16import * as fs from 'fs';
17import * as path from 'path';
18import * as JSON5 from 'json5';
19import { BuildConfig } from './types';
20
21export interface ModuleDescriptor {
22  arktsversion: string;
23  name: string;
24  moduleType: string;
25  srcPath: string;
26}
27
28interface Json5Object {
29  module?: {
30    type?: string;
31  };
32  modules?: Array<{
33    name: string;
34    srcPath: string;
35    arktsversion?: string;
36  }>;
37  dependencies?: {
38    [packageName: string]: string;
39  };
40}
41
42function removeTrailingCommas(jsonString: string): string {
43  return jsonString.replace(/,\s*([}\]])/g, '$1');
44}
45
46function parseJson5(filePath: string): Json5Object {
47  try {
48    const rawContent = fs.readFileSync(filePath, 'utf8');
49    const cleanedContent = removeTrailingCommas(rawContent);
50    return JSON5.parse(cleanedContent) as Json5Object;
51  } catch (error) {
52    console.error(`Error parsing ${filePath}:`, error);
53    return {} as Json5Object;
54  }
55}
56
57function getModuleTypeFromConfig(modulePath: string): string {
58  const moduleConfigPath = path.join(modulePath, 'src/main/module.json5');
59  if (fs.existsSync(moduleConfigPath)) {
60    try {
61      const moduleData = parseJson5(moduleConfigPath);
62      return moduleData.module?.type || 'har';
63    } catch (error) {
64      console.error(`Error parsing ${moduleConfigPath}:`, error);
65    }
66  }
67  return 'har';
68}
69
70function getModulesFromBuildProfile(buildProfilePath: string): ModuleDescriptor[] {
71  if (!fs.existsSync(buildProfilePath)) {
72    console.error('Error: build-profile.json5 not found');
73    process.exit(1);
74  }
75
76  const buildProfile = parseJson5(buildProfilePath);
77  const modules = buildProfile.modules || [];
78
79  return modules.map((module: { name: string; srcPath: string; arktsversion?: string }) => {
80    const moduleSrcPath = path.resolve(path.dirname(buildProfilePath), module.srcPath);
81    const arktsversion = module.arktsversion || '1.1';
82    return {
83      name: module.name,
84      moduleType: getModuleTypeFromConfig(moduleSrcPath),
85      srcPath: moduleSrcPath,
86      arktsversion
87    };
88  });
89}
90
91function getEtsFiles(modulePath: string): string[] {
92  const files: string[] = [];
93
94  const shouldSkipDirectory = (relativePath: string): boolean => {
95    const testDir1 = `src${path.sep}test`;
96    const testDir2 = `src${path.sep}ohosTest`;
97    return relativePath.startsWith(testDir1) || relativePath.startsWith(testDir2);
98  };
99
100  const processEntry = (dir: string, entry: fs.Dirent): void => {
101    const fullPath = path.join(dir, entry.name);
102    const relativePath = path.relative(modulePath, fullPath);
103
104    if (entry.isDirectory()) {
105      if (shouldSkipDirectory(relativePath)) {
106        return;
107      }
108      traverseDir(fullPath);
109      return;
110    }
111
112    if (entry.isFile() && entry.name.endsWith('.ets')) {
113      files.push(fullPath);
114    }
115  };
116
117  const traverseDir = (dir: string): void => {
118    if (!fs.existsSync(dir)) {
119      return;
120    }
121
122    const entries = fs.readdirSync(dir, { withFileTypes: true });
123    entries.forEach((entry) => processEntry(dir, entry));
124  };
125
126  traverseDir(modulePath);
127  return files;
128}
129
130function getModuleDependencies(modulePath: string, visited = new Set<string>()): string[] {
131  if (visited.has(modulePath)) {
132    return [];
133  }
134  visited.add(modulePath);
135
136  const extractDependencies = (): string[] => {
137    const packageFilePath = path.join(modulePath, 'oh-package.json5');
138    if (!fs.existsSync(packageFilePath)) {
139      return [];
140    }
141
142    try {
143      const packageData = parseJson5(packageFilePath);
144      return Object.entries(packageData.dependencies || {})
145        .map(([_, depPath]) => (depPath as string).replace('file:', ''))
146        .map((depPath) => path.resolve(modulePath, depPath));
147    } catch (error) {
148      console.error(`Error parsing ${packageFilePath}:`, error);
149      return [];
150    }
151  };
152
153  const resolveNestedDependencies = (deps: string[]): string[] => {
154    return deps.flatMap((depPath) =>
155      visited.has(depPath) ? [] : [depPath, ...getModuleDependencies(depPath, visited)]
156    );
157  };
158
159  const dependencies = extractDependencies();
160  const nestedDependencies = resolveNestedDependencies(dependencies);
161  return Array.from(new Set([...dependencies, ...nestedDependencies]));
162}
163
164function createMapEntryForPlugin(buildSdkPath: string, pluginName: string): string {
165  return path.join(buildSdkPath, 'build-tools', 'ui-plugins', 'lib', pluginName, 'index');
166}
167
168function createPluginMap(buildSdkPath: string): Record<string, string> {
169  let pluginMap: Record<string, string> = {};
170  const pluginList: string[] = ['ui-syntax-plugins', 'ui-plugins', 'memo-plugins'];
171  for (const plugin of pluginList) {
172    pluginMap[plugin] = createMapEntryForPlugin(buildSdkPath, plugin);
173  }
174  return pluginMap;
175}
176
177export function generateBuildConfigs(
178  buildSdkPath: string,
179  projectRoot: string,
180  modules?: ModuleDescriptor[]
181): Record<string, BuildConfig> {
182  const allBuildConfigs: Record<string, BuildConfig> = {};
183
184  if (!modules) {
185    const buildProfilePath = path.join(projectRoot, 'build-profile.json5');
186    modules = getModulesFromBuildProfile(buildProfilePath);
187  }
188
189  const definedModules = modules;
190  const cacheDir = path.join(projectRoot, '.idea', '.deveco');
191
192  for (const module of definedModules) {
193    const modulePath = module.srcPath;
194    const compileFiles = new Set(getEtsFiles(modulePath));
195    const pluginMap = createPluginMap(buildSdkPath);
196
197    // Get recursive dependencies
198    const dependencies = getModuleDependencies(modulePath);
199    for (const depPath of dependencies) {
200      getEtsFiles(depPath).forEach((file) => compileFiles.add(file));
201    }
202
203    allBuildConfigs[module.name] = {
204      plugins: pluginMap,
205      arkts: {},
206      arktsGlobal: {},
207      compileFiles: Array.from(compileFiles),
208      packageName: module.name,
209      moduleType: module.moduleType,
210      buildType: 'build',
211      buildMode: 'Debug',
212      moduleRootPath: modulePath,
213      sourceRoots: ['./'],
214      hasMainModule: true,
215      loaderOutPath: path.join(modulePath, 'build', 'default', 'cache'),
216      cachePath: cacheDir,
217      buildSdkPath: buildSdkPath,
218      enableDeclgenEts2Ts: false,
219      declgenDtsOutPath: path.join(modulePath, 'build', 'default', 'cache'),
220      declgenTsOutPath: path.join(modulePath, 'build', 'default', 'cache'),
221      dependentModuleList: dependencies.map((dep) => {
222        const depModule = definedModules.find((m) => m.srcPath === dep);
223        return {
224          packageName: path.basename(dep),
225          moduleName: path.basename(dep),
226          moduleType: depModule ? depModule.moduleType : 'har',
227          modulePath: dep,
228          sourceRoots: ['./'],
229          entryFile: 'index.ets',
230          language: depModule ? depModule.arktsversion : '1.1'
231        };
232      })
233    };
234  }
235  const outputPath = path.join(cacheDir, 'lsp_build_config.json');
236  if (!fs.existsSync(cacheDir)) {
237    fs.mkdirSync(cacheDir, { recursive: true });
238  }
239  fs.writeFileSync(outputPath, JSON.stringify(allBuildConfigs, null, 4));
240  return allBuildConfigs;
241}
242