• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# Copyright (c) 2025 Huawei Device Co., Ltd.
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16# Description
17#
18# This script is invoked by the build system and does not need to be executed directly by the developer.
19# First, it checks if --release is provided as an argument. This is the only allowed type for stateMgmt
20# that is included in the build image. It then verifies if the node_modules folder exists. If not, npm
21# install is executed. Afterward, npm run build_release is performed, which also generates generateGni.js
22# The files_to_watch.gni file contains a list of input files from tsconfig.base.json. When any of these
23# files are modified, the build system triggers this script to regenerate stateMgmt.js.
24
25import os
26import sys
27import json
28import time
29import shutil
30import subprocess
31from typing import Dict, List
32
33from preprocess import merge_component
34
35
36TARGET_CMD = "arkoala:abc"
37ABC_FILE = "arkoala.abc"
38NPM_REPO = "https://repo.huaweicloud.com/repository/npm/"
39
40
41class Paths:
42    def __init__(self):
43        self.project_path = None
44        self.build_path = None
45        self.node_path = None
46        self.output_target_path = None
47        self.es2panda_path = None
48        self.ohos_ets_api_path = None
49        self.ohos_ets_arkts_path = None
50        self.arklink_path = None
51        self.ets_stdlib_path = None
52        self.innerkits_path = None
53        self.check_install_path = None
54        self.check_bin_path = None
55        self.dist_path = None
56        self.logfile = None
57        self.built_path = None
58        self.check_fast_arktsc = None
59        self.arkui_ohos_path = None
60
61
62def parse_argv(argv) -> Paths:
63    """
64    parse command line arguments
65    """
66    if len(argv) < 4:
67        print("Usage: python build.py <path_to_project> <build_path> <node_path> <output_target_path>")
68        sys.exit(1)
69
70    path = Paths()
71    path.project_path = os.path.abspath(argv[1])
72    path.build_path = os.path.abspath(argv[2])
73    path.node_path = os.path.abspath(argv[3])
74    path.output_target_path = os.path.abspath(argv[4])
75    path.es2panda_path = os.path.abspath(argv[5])
76    path.ohos_ets_api_path = os.path.abspath(argv[6])
77    path.ohos_ets_arkts_path = os.path.abspath(argv[7])
78    path.arklink_path = os.path.abspath(argv[8])
79    path.ets_stdlib_path = os.path.abspath(argv[9])
80    path.innerkits_path = os.path.abspath(argv[10])
81    path.check_install_path = os.path.join(path.project_path, "arkoala-arkts", "node_modules")
82    path.check_bin_path = os.path.join(path.check_install_path, ".bin")
83    path.check_fast_arktsc = os.path.join(path.check_bin_path, "fast-arktsc")
84    path.dist_path = os.path.join(path.build_path, "dist")
85    path.logfile = os.path.join(path.dist_path, "koala_build.log")
86    path.arkui_ohos_path = os.path.join(path.project_path, "arkoala-arkts" ,"arkui-ohos-preprocess")
87    return path
88
89
90def prebuilt_dist(path: Paths):
91    """
92    create log file and prebuilt dist path
93    """
94    if os.path.exists(path.logfile):
95        try:
96            os.remove(path.logfile)
97        except OSError as e:
98            print("remove log file filed!")
99    if not os.path.exists(path.dist_path):
100        os.makedirs(path.dist_path)
101    with open(path.logfile, "a+") as f:
102        f.write("koala build:\n")
103        f.write(path.logfile + "\n")
104        f.write("es2panda_path: " + path.es2panda_path + "\n")
105        f.write("arklink_path:" + path.arklink_path + "\n")
106        f.write("ets_stdlib_path:" + path.ets_stdlib_path + "\n")
107        f.close()
108
109
110def change_env_path(env, path: Paths):
111    """
112    change env path for es2pand and arklink process
113    """
114    env["PATH"] = f"{path.node_path}:{env['PATH']}"
115    if (path.es2panda_path != ""):
116        env["ES2PANDA_PATH"] = path.es2panda_path
117    if (path.arklink_path != ""):
118        env["ARKLINK_PATH"] = path.arklink_path
119    if (path.ets_stdlib_path != ""):
120        env["ETS_STDLIB_PATH"] = path.ets_stdlib_path
121    try:
122        ret = subprocess.run(["npm", "-v"], capture_output=True, env = env, text=True, check=True)
123        with open(path.logfile, "a+") as f:
124            f.write("\n")
125            f.write("path env:" + env["PATH"] + "\n")
126            f.write("npm version:" + ret.stdout)
127            print(f"npm version: {ret.stdout}")
128            f.close()
129    except subprocess.CalledProcessError as e:
130        with open(path.logfile, "a+") as f:
131            f.write("\n")
132            f.write("error message: "+ e.stderr + "\n")
133            print(f"error message: {e.stderr}")
134            f.close()
135    os.chdir(path.project_path)
136
137
138def check_node_modules(env, path: Paths):
139    """
140    Check if `node_modules` exists. If yes skip npm install
141    """
142    if not os.path.exists(path.check_install_path):
143        try:
144            ret = subprocess.run(
145                ["npm", "install", "--registry", NPM_REPO, "--verbose"],
146                capture_output=True,
147                env = env,
148                text=True,
149                check=True
150            )
151            with open(path.logfile, "a+") as f:
152                f.write("\n")
153                f.write("install log:\n" + ret.stdout)
154                print(f"install log:\n {ret.stdout}")
155                f.close()
156        except subprocess.CalledProcessError as e:
157            with open(path.logfile, "a+") as f:
158                f.write("\n")
159                f.write("error message: "+ e.stderr + "\n")
160                print(f"error message: {e.stderr}")
161                f.close()
162    else:
163        print(f"arkola node_modules directory exists, skip install")
164        with open(path.logfile, "a+") as f:
165            f.write("\n")
166            f.write("arkola node_modules directory exists, skip install\n")
167            f.close()
168
169
170def check_fast_arktsc(path: Paths):
171    """
172    check fast arktsc
173    """
174    if not os.path.exists(path.check_fast_arktsc):
175        print(f"fast-arktsc not found!")
176        with open(path.logfile, "a+") as f:
177            f.write("\n")
178            f.write("fast-arktsc not found!\n")
179            f.close()
180
181
182def is_target_file(file_name: str) -> bool:
183    """
184    Check if the given file name is a target file.
185    """
186    target_extensions = [".d.ets", ".ets"]
187    return any(file_name.endswith(ext) for ext in target_extensions)
188
189
190def get_key_from_file_name(file_name: str) -> str:
191    """
192    Extract the key from the given file name.
193    """
194    if ".d." in file_name:
195        file_name = file_name.replace(".d.", ".")
196    return os.path.splitext(file_name)[0]
197
198
199def scan_directory_for_paths(directory: str) -> Dict[str, List[str]]:
200    """
201    Scan the specified directory to find all target files and organize their paths by key.
202    """
203    paths = {}
204    for root, _, files in os.walk(directory):
205        for file in files:
206            if not is_target_file(file):
207                continue
208            file_path = os.path.abspath(os.path.join(root, file))
209            file_name = get_key_from_file_name(file)
210            file_abs_path = os.path.abspath(os.path.join(root, file_name))
211            file_rel_path = os.path.relpath(file_abs_path, start=directory)
212            # Split the relative path into components
213            path_components = file_rel_path.split(os.sep)
214            first_level_dir = path_components[0] if len(path_components) > 0 else ""
215            second_level_dir = path_components[1] if len(path_components) > 1 else ""
216            # Determine the key based on directory structure
217            if first_level_dir == "arkui" and second_level_dir == "runtime-api":
218                key = file_name
219            else:
220                key = file_rel_path.replace(os.sep, ".")
221            if key in paths:
222                paths[key].append(file_path)
223            else:
224                paths[key] = [file_path]
225    return paths
226
227
228def generate_new_arkts_config(path: Paths):
229    """
230    generate new arkts config json
231    """
232    paths = {}
233    new_paths = {}
234    if path.ohos_ets_api_path == "" or path.ohos_ets_arkts_path == "":
235        print(f"ohos ets api or arkts path not exists")
236        sys.exit(1)
237    else:
238        scan_paths = [path.ohos_ets_api_path, path.ohos_ets_arkts_path]
239        for scan_path in scan_paths:
240            scanned_paths = scan_directory_for_paths(scan_path)
241            for key, value in scanned_paths.items():
242                if key in paths:
243                    paths[key].extend(value)
244                else:
245                    paths[key] = value
246    old_arkts_config_path = os.path.join(path.arkui_ohos_path, "arktsconfig-unmemoized.json")
247    new_arkts_config_path = os.path.join(path.arkui_ohos_path, "arktsconfig-unmemoized-merged.json")
248    # need take old arkts config json paths splicing to new arkts config paths
249    with open(old_arkts_config_path, 'r', encoding='utf-8') as f:
250        old_data = json.load(f)
251        old_paths = old_data['compilerOptions']['paths']
252        for key, value in paths.items():
253            if key not in old_paths:
254                old_paths[key] = value
255        with open(new_arkts_config_path, 'w', encoding="utf-8") as f:
256            json.dump(old_data, f, indent=2, ensure_ascii=False)
257
258
259def run_build_arkoala(env, path: Paths):
260    """
261    run build arkoala
262    """
263    try:
264        ret = subprocess.run(
265            ["npm", "run", TARGET_CMD, "--verbose"],
266            capture_output=True,
267            env = env,
268            text=True,
269            check=True
270        )
271        with open(path.logfile, "a+") as f:
272            f.write("\n")
273            f.write("build log:\n" + ret.stdout)
274            print(f"build log:\n {ret.stdout}")
275            f.close()
276    except subprocess.CalledProcessError as e:
277        with open(path.logfile, "a+") as f:
278            f.write("\n")
279            f.write("build log: "+ e.stdout + "\n")
280            f.write("error message: "+ e.stderr + "\n")
281            print(f"build log:\n {e.stdout}")
282            print(f"error message: {e.stderr}")
283            f.close()
284
285
286def copy_akoala_abc(path: Paths):
287    """
288    copy arkoala abc file and move to dist path
289    """
290    built_path = os.path.join(path.project_path, "arkoala-arkts", "build", ABC_FILE)
291    if not os.path.exists(built_path):
292        print(f"Error: Built file not found at {built_path}")
293        sys.exit(1)
294    dist_file = os.path.join(path.dist_path, ABC_FILE)
295    output_file = os.path.join(path.output_target_path, ABC_FILE)
296    try:
297        shutil.copy(built_path, dist_file)
298        shutil.copy(built_path, output_file)
299        print(f"Arkoala: File successfully copied to {dist_file}")
300        with open(path.logfile, "a+") as f:
301            f.write("\n")
302            f.write("Arkoala: File successfully copied to " + dist_file + " \n")
303            f.write("Arkoala: File successfully copied to " + output_file + " \n")
304            f.close()
305    except Exception as e:
306        print(f"Error: Failed to copy file: {e}")
307        sys.exit(1)
308
309
310def resolve_innerkis_config(path: Paths, config_file, output_file):
311    config_file_path = os.path.join(path.arkui_ohos_path, config_file)
312    with open(path.logfile, "a+") as f:
313        f.write("\n")
314        f.write("resolve config path: " + config_file_path + " \n")
315        f.close()
316
317    try:
318        with open(config_file_path, 'r', encoding='utf-8') as file:
319            with open(path.logfile, "a+") as f:
320                f.write("resolved path file opend \n")
321                f.close()
322            json_data = json.load(file)
323
324            # 获取 baseUrl
325            base_url = json_data['compilerOptions']['baseUrl']
326
327            # 获取配置文件的目录路径
328            config_dir = os.path.dirname(config_file_path)
329
330            # 将 baseUrl 解析为绝对路径(相对于配置文件)
331            absolute_base_url = os.path.abspath(os.path.join(config_dir, base_url))
332
333            # 获取 paths
334            paths = json_data['compilerOptions']['paths']
335
336            # 处理 paths 中的相对路径
337            resolved_paths = {}
338            for key, value in paths.items():
339                resolved_value = [os.path.abspath(os.path.join(absolute_base_url, path)) for path in value]
340                resolved_paths[key] = resolved_value
341            if not os.path.exists(path.innerkits_path):
342                os.makedirs(path.innerkits_path)
343            with open(output_file, 'w', encoding='utf-8') as f:
344                json.dump({"paths": resolved_paths}, f, indent=2, ensure_ascii=False)
345            with open(path.logfile, "a+") as f:
346                f.write("output_file generated:" + output_file + " \n")
347                f.close()
348    except Exception as e:
349        with open(path.logfile, "a+") as f:
350            f.write("Error: Failed to generate innerkit path file: " + e + " \n")
351            f.close()
352        sys.exit(1)
353
354
355def pre_processing(path: Paths):
356    start_time = time.time()
357    original_path = os.path.join(
358        path.project_path, "arkoala-arkts", "arkui-ohos")
359    target_path = os.path.join(
360        path.project_path, "arkoala-arkts", "arkui-ohos-preprocess")
361
362    if os.path.exists(target_path):
363        shutil.rmtree(target_path)
364
365    def ignore_build_path(src, names):
366        return ["build"] if "build" in names else []
367
368    shutil.copytree(original_path, target_path, ignore=ignore_build_path, dirs_exist_ok=True)
369
370    handwritten_path = os.path.join(target_path, "src", "handwritten", "component")
371    generated_path = os.path.join(target_path, "src", "component")
372
373    merge_component(handwritten_path, generated_path)
374
375    shutil.rmtree(handwritten_path)
376    end_time = time.time()
377    elapsed_time = end_time - start_time
378    print(f"Arkoala: preprocess time: {elapsed_time:.2f} seconds")
379    return
380
381def main(argv):
382    path = parse_argv(argv)
383    pre_processing(path)
384    prebuilt_dist(path)
385    env = os.environ.copy()
386    env_old_path = env["PATH"]
387    change_env_path(env, path)
388    check_node_modules(env, path)
389    check_fast_arktsc(path)
390    generate_new_arkts_config(path)
391    run_build_arkoala(env, path)
392    env["PATH"] = env_old_path
393    copy_akoala_abc(path)
394    resolve_innerkis_config(path, "arktsconfig-unmemoized-merged.json", os.path.join(path.innerkits_path, "arkts_paths.json"))
395
396
397if __name__ == '__main__':
398    start_time = time.time()
399    main(sys.argv)
400    end_time = time.time()
401    elapsed_time = end_time - start_time
402    print(f"Arkoala: build time: {elapsed_time:.2f} seconds")
403