1# coding=utf-8 2# 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 16import argparse 17import json 18import logging 19import os 20import shutil 21import subprocess 22import sys 23import tarfile 24import time 25from collections.abc import Iterable, Mapping, Sequence 26from dataclasses import dataclass 27from enum import Enum 28from pathlib import Path 29 30from taihe.driver.backend import BackendRegistry 31from taihe.driver.contexts import CompilerInstance, CompilerInvocation 32from taihe.utils.logging import setup_logger 33from taihe.utils.outputs import CMakeOutputConfig, DebugLevel, OutputConfig 34from taihe.utils.resources import ResourceLocator, ResourceType 35 36# A lower value means more verbosity 37TRACE_CONCISE = logging.DEBUG - 1 38TRACE_VERBOSE = TRACE_CONCISE - 1 39 40 41class UserType(Enum): 42 """User type for the build system.""" 43 44 STS = "sts" 45 CPP = "cpp" 46 47 48@dataclass 49class UserInfo: 50 """User information for authentication.""" 51 52 username: str 53 password: str 54 55 56class BuildUtils: 57 """Utility class for common operations.""" 58 59 def __init__(self, verbosity: int): 60 setup_logger(verbosity) 61 self.logger = logging.getLogger("build_system") 62 63 def run_command( 64 self, 65 command: Sequence[Path | str], 66 capture_output: bool = False, 67 env: Mapping[str, Path | str] | None = None, 68 ) -> float: 69 """Run a command with environment variables.""" 70 command_str = " ".join(map(str, command)) 71 72 env_str = "" 73 for key, val in (env or {}).items(): 74 env_str += f"{key}={val} " 75 76 self.logger.debug("+ %s%s", env_str, command_str) 77 78 try: 79 start_time = time.time() 80 subprocess.run( 81 command, 82 check=True, 83 text=True, 84 env=env, 85 capture_output=capture_output, 86 ) 87 end_time = time.time() 88 elapsed_time = end_time - start_time 89 return elapsed_time 90 except subprocess.CalledProcessError as e: 91 self.logger.error("Command failed with exit code %s", e.returncode) 92 if e.stdout: 93 self.logger.error("Standard output: %s", e.stdout) 94 if e.stderr: 95 self.logger.error("Standard error: %s", e.stderr) 96 raise 97 98 def create_directory(self, directory: Path) -> None: 99 directory.mkdir(parents=True, exist_ok=True) 100 self.logger.debug("Created directory: %s", directory) 101 102 def clean_directory(self, directory: Path) -> None: 103 if not directory.exists(): 104 return 105 shutil.rmtree(directory) 106 self.logger.debug("Cleaned directory: %s", directory) 107 108 def move_directory(self, src: Path, dst: Path) -> None: 109 if not src.exists(): 110 raise FileNotFoundError(f"Source directory does not exist: {src}") 111 shutil.move(src, dst) 112 self.logger.debug("Moved directory from %s to %s", src, dst) 113 114 def copy_directory(self, src: Path, dst: Path) -> None: 115 if not src.exists(): 116 raise FileNotFoundError(f"Source directory does not exist: {src}") 117 shutil.copytree(src, dst, dirs_exist_ok=True) 118 self.logger.debug("Copied directory from %s to %s", src, dst) 119 120 def download_file( 121 self, 122 target_file: Path, 123 url: str, 124 user_info: UserInfo | None = None, 125 ) -> None: 126 """Download a file from a URL.""" 127 if target_file.exists(): 128 self.logger.info("Already found %s, skipping download", target_file) 129 return 130 131 temp_file = target_file.with_suffix(".tmp") 132 133 command = ["curl", "-L", "--progress-bar", url, "-o", temp_file] 134 135 if user_info: 136 command.extend(["-u", f"{user_info.username}:{user_info.password}"]) 137 138 self.run_command(command) 139 140 if temp_file.exists(): 141 temp_file.rename(target_file) 142 self.logger.info("Downloaded %s to %s", url, target_file) 143 else: 144 self.logger.error("Failed to download %s", url) 145 raise FileNotFoundError(f"Failed to download {url}") 146 147 def extract_file( 148 self, 149 target_file: Path, 150 extract_dir: Path, 151 ) -> None: 152 """Extract a tar.gz file.""" 153 if not target_file.exists(): 154 raise FileNotFoundError(f"File to extract does not exist: {target_file}") 155 156 self.create_directory(extract_dir) 157 158 with tarfile.open(target_file, "r:gz") as tar: 159 # Check for any unsafe paths before extraction 160 for member in tar.getmembers(): 161 member_path = Path(member.name) 162 if member_path.is_absolute() or ".." in member_path.parts: 163 raise ValueError(f"Unsafe path in archive: {member.name}") 164 # Extract safely 165 tar.extractall(path=extract_dir) 166 167 self.logger.info("Extracted %s to %s", target_file, extract_dir) 168 169 170class BuildConfig: 171 """Configuration for the build process.""" 172 173 def __init__(self): 174 self.cxx = os.getenv("CXX", "clang++") 175 self.cc = os.getenv("CC", "clang") 176 self.panda_userinfo = UserInfo( 177 username=os.getenv("PANDA_USERNAME", "koala-pub"), 178 password=os.getenv("PANDA_PASSWORD", "y3t!n0therP"), 179 ) 180 self.panda_url = "https://nexus.bz-openlab.ru:10443/repository/koala-npm/%40panda/sdk/-/sdk-1.5.0-dev.36922.tgz" 181 182 self.locator = ResourceLocator.detect() 183 self.panda_extract_dir = self.locator.get(ResourceType.DEV_PANDA_VM) 184 self.panda_package_dir = self.panda_extract_dir / "package" 185 self.panda_ets_dir = self.panda_package_dir / "ets" 186 self.panda_tool_dir = self.panda_package_dir / "linux_host_tools" 187 self.panda_include_dir = ( 188 self.panda_package_dir / "ohos_arm64/include/plugins/ets/runtime/ani" 189 ) 190 # TODO this should be removed 191 self.taihe_version_file = self.locator.root_dir / "version.txt" 192 self.panda_version_file = self.panda_package_dir / "version.txt" 193 194 195def _map_output_debug_level(verbosity: int) -> DebugLevel: 196 if verbosity <= TRACE_VERBOSE: 197 return DebugLevel.VERBOSE 198 if verbosity <= TRACE_CONCISE: 199 return DebugLevel.CONCISE 200 return DebugLevel.NONE 201 202 203class BuildSystem(BuildUtils): 204 """Main build system class.""" 205 206 def __init__( 207 self, 208 target_dir: str, 209 user: UserType, 210 config: BuildConfig, 211 verbosity: int = logging.INFO, 212 ): 213 super().__init__(verbosity) 214 self.config = config 215 self.should_run_pretty_print = verbosity <= logging.DEBUG 216 self.codegen_debug_level = _map_output_debug_level(verbosity) 217 218 self.user = user 219 220 # Build paths 221 self.target_path = Path(target_dir).resolve() 222 223 self.idl_dir = self.target_path / "idl" 224 self.build_dir = self.target_path / "build" 225 226 self.generated_dir = self.target_path / "generated" 227 self.generated_include_dir = self.generated_dir / "include" 228 self.generated_src_dir = self.generated_dir / "src" 229 230 self.author_dir = self.target_path / "author" 231 self.author_include_dir = self.author_dir / "include" 232 self.author_src_dir = self.author_dir / "src" 233 234 self.user_dir = self.target_path / "user" 235 self.user_include_dir = self.user_dir / "include" 236 self.user_src_dir = self.user_dir / "src" 237 238 self.runtime_includes = [self.config.locator.get(ResourceType.RUNTIME_HEADER)] 239 if self.user == UserType.STS: 240 self.runtime_includes.append(self.config.panda_include_dir) 241 self.generated_includes = [*self.runtime_includes, self.generated_include_dir] 242 self.author_includes = [*self.generated_includes, self.author_include_dir] 243 self.user_includes = [*self.generated_includes, self.user_include_dir] 244 245 # Build sub-directories 246 self.build_generated_src_dir = self.build_dir / "generated" / "src" 247 self.build_author_src_dir = self.build_dir / "author" / "src" 248 self.build_runtime_src_dir = self.build_dir / "runtime" / "src" 249 self.build_generated_dir = self.build_dir / "generated" 250 self.build_user_dir = self.build_dir / "user" 251 252 # Output files 253 self.lib_name = self.target_path.absolute().name 254 self.so_target = self.build_dir / f"lib{self.lib_name}.so" 255 self.abc_target = self.build_dir / "main.abc" 256 self.exe_target = self.build_dir / "main" 257 self.arktsconfig_file = self.build_dir / "arktsconfig.json" 258 259 def create(self) -> None: 260 """Create a simple example project.""" 261 self.create_idl() 262 self.create_author_cpp() 263 if self.user == UserType.STS: 264 self.create_user_ets() 265 if self.user == UserType.CPP: 266 self.create_user_cpp() 267 268 def create_idl(self) -> None: 269 """Create a simple example IDL file.""" 270 self.create_directory(self.idl_dir) 271 with open(self.idl_dir / "hello.taihe", "w") as f: 272 f.write(f"function sayHello(): void;\n") 273 274 def create_author_cpp(self) -> None: 275 """Create a simple example author source file.""" 276 self.create_directory(self.author_src_dir) 277 with open(self.author_dir / "compile_flags.txt", "w") as f: 278 for author_include_dir in self.author_includes: 279 f.write(f"-I{author_include_dir}\n") 280 with open(self.author_src_dir / "hello.impl.cpp", "w") as f: 281 f.write( 282 f'#include "hello.proj.hpp"\n' 283 f'#include "hello.impl.hpp"\n' 284 f"\n" 285 f"#include <iostream>\n" 286 f"\n" 287 f"void sayHello() {{\n" 288 f' std::cout << "Hello, World!" << std::endl;\n' 289 f" return;\n" 290 f"}}\n" 291 f"\n" 292 f"TH_EXPORT_CPP_API_sayHello(sayHello);\n" 293 ) 294 295 def create_user_ets(self) -> None: 296 """Create a simple example user ETS file.""" 297 self.create_directory(self.user_dir) 298 with open(self.user_dir / "main.ets", "w") as f: 299 f.write( 300 f'import * as hello from "hello";\n' 301 f"\n" 302 f'loadLibrary("{self.lib_name}");\n' 303 f"\n" 304 f"function main() {{\n" 305 f" hello.sayHello();\n" 306 f"}}\n" 307 ) 308 309 def create_user_cpp(self) -> None: 310 """Create a simple example user source file.""" 311 self.create_directory(self.user_src_dir) 312 with open(self.user_dir / "compile_flags.txt", "w") as f: 313 for user_include_dir in self.user_includes: 314 f.write(f"-I{user_include_dir}\n") 315 with open(self.user_src_dir / "main.cpp", "w") as f: 316 f.write( 317 f'#include "hello.user.hpp"\n' 318 f"\n" 319 f"int main() {{\n" 320 f" hello::sayHello();\n" 321 f" return 0;\n" 322 f"}}\n" 323 ) 324 325 def generate(self, sts_keep_name: bool, cmake: bool) -> None: 326 """Generate code from IDL files.""" 327 if not self.idl_dir.is_dir(): 328 raise FileNotFoundError(f"IDL directory not found: '{self.idl_dir}'") 329 330 self.clean_directory(self.generated_dir) 331 332 self.logger.info("Generating author and ani codes...") 333 334 registry = BackendRegistry() 335 registry.register_all() 336 backend_names = ["cpp-author"] 337 if self.user == UserType.STS: 338 backend_names.append("ani-bridge") 339 if self.user == UserType.CPP: 340 backend_names.append("cpp-user") 341 if self.should_run_pretty_print: 342 backend_names.append("pretty-print") 343 backends = registry.collect_required_backends(backend_names) 344 resolved_backends = [b() for b in backends] 345 346 if cmake: 347 output_config = CMakeOutputConfig( 348 dst_dir=Path(self.generated_dir), 349 runtime_include_dir=self.config.locator.get( 350 ResourceType.RUNTIME_HEADER 351 ), 352 runtime_src_dir=self.config.locator.get(ResourceType.RUNTIME_SOURCE), 353 ) 354 else: 355 output_config = OutputConfig( 356 dst_dir=Path(self.generated_dir), 357 ) 358 359 invocation = CompilerInvocation( 360 src_files=[ 361 src_file 362 for src_dir in [ 363 self.idl_dir, 364 self.config.locator.get(ResourceType.STDLIB), 365 ] 366 for src_file in src_dir.glob("*.taihe") 367 ], 368 output_config=output_config, 369 backends=resolved_backends, 370 sts_keep_name=sts_keep_name, 371 ) 372 373 instance = CompilerInstance(invocation) 374 if not instance.run(): 375 raise RuntimeError(f"Code generation failed") 376 377 def build(self, opt_level: str) -> None: 378 """Run the complete build process.""" 379 self.logger.info("Starting ANI compilation...") 380 381 self.setup_build_directories() 382 383 if self.user == UserType.STS: 384 # Set up paths for Panda VM 385 self.prepare_panda_vm() 386 387 # Compile the shared library 388 self.compile_shared_library(opt_level=opt_level) 389 390 # Compile and link ABC files 391 self.compile_and_link_ani() 392 393 # Run with Ark runtime 394 self.run_ani() 395 elif self.user == UserType.CPP: 396 # Compile the shared library 397 self.compile_shared_library(opt_level=opt_level) 398 399 # Compile the executable 400 self.compile_and_link_exe(opt_level=opt_level) 401 402 # Run the executable 403 self.run_exe() 404 405 self.logger.info("Build and execution completed successfully") 406 407 def compile_shared_library(self, opt_level: str): 408 """Compile the shared library.""" 409 self.logger.info("Compiling shared library...") 410 411 runtime_src_dir = self.config.locator.get(ResourceType.RUNTIME_SOURCE) 412 runtime_sources = [ 413 runtime_src_dir / "string.cpp", 414 runtime_src_dir / "object.cpp", 415 ] 416 if self.user == UserType.STS: 417 runtime_sources.append(runtime_src_dir / "runtime.cpp") 418 419 # Compile each component 420 runtime_objects = self.compile( 421 self.build_runtime_src_dir, 422 runtime_sources, 423 self.runtime_includes, 424 compile_flags=[f"-O{opt_level}"], 425 ) 426 generated_objects = self.compile( 427 self.build_generated_src_dir, 428 self.generated_src_dir.glob("*.[cC]*"), 429 self.generated_includes, 430 compile_flags=[f"-O{opt_level}"], 431 ) 432 author_objects = self.compile( 433 self.build_author_src_dir, 434 self.author_src_dir.glob("*.[cC]*"), 435 self.author_includes, 436 compile_flags=[f"-O{opt_level}"], 437 ) 438 439 # Link all objects 440 if all_objects := runtime_objects + generated_objects + author_objects: 441 self.link( 442 self.so_target, 443 all_objects, 444 shared=True, 445 link_options=["-Wl,--no-undefined"], 446 ) 447 self.logger.info("Shared library compiled: %s", self.so_target) 448 else: 449 self.logger.warning( 450 "No object files to link, skipping shared library compilation" 451 ) 452 453 def compile_and_link_exe(self, opt_level: str) -> None: 454 """Compile and link the executable.""" 455 self.logger.info("Compiling and linking executable...") 456 457 # Compile the user source files 458 user_objects = self.compile( 459 self.build_user_dir, 460 self.user_src_dir.glob("*.[cC]*"), 461 self.user_includes, 462 compile_flags=[f"-O{opt_level}"], 463 ) 464 465 # Link the executable 466 if user_objects: 467 self.link( 468 self.exe_target, 469 [self.so_target, *user_objects], 470 ) 471 self.logger.info("Executable compiled: %s", self.so_target) 472 else: 473 self.logger.warning( 474 "No object files to link, skipping executable compilation" 475 ) 476 477 def run_exe(self) -> None: 478 """Run the compiled executable.""" 479 self.logger.info("Running executable...") 480 481 elapsed_time = self.run( 482 self.exe_target, 483 self.so_target.parent, 484 ) 485 486 self.logger.info("Done, time = %f s", elapsed_time) 487 488 def compile_and_link_ani(self): 489 """Compile and link ABC files.""" 490 self.logger.info("Compiling and linking ABC files...") 491 492 paths: dict[str, Path] = {} 493 for path in self.generated_dir.glob("*.ets"): 494 paths[path.stem] = path 495 for path in self.user_dir.glob("*.ets"): 496 paths[path.stem] = path 497 498 self.create_arktsconfig(self.arktsconfig_file, paths) 499 500 # Compile ETS files in each directory 501 generated_abc = self.compile_abc( 502 self.build_generated_dir, 503 self.generated_dir.glob("*.ets"), 504 self.arktsconfig_file, 505 ) 506 user_abc = self.compile_abc( 507 self.build_user_dir, 508 self.user_dir.glob("*.ets"), 509 self.arktsconfig_file, 510 ) 511 512 # Link all ABC files 513 if all_abc_files := generated_abc + user_abc: 514 self.link_abc( 515 self.abc_target, 516 all_abc_files, 517 ) 518 self.logger.info("ABC files linked: %s", self.abc_target) 519 else: 520 self.logger.warning("No ABC files to link, skipping ABC compilation") 521 522 def run_ani(self) -> None: 523 """Run the compiled ABC file with the Ark runtime.""" 524 self.logger.info("Running ABC file with Ark runtime...") 525 526 elapsed_time = self.run_abc( 527 self.abc_target, 528 self.so_target.parent, 529 entry="main.ETSGLOBAL::main", 530 ) 531 532 self.logger.info("Done, time = %f s", elapsed_time) 533 534 def setup_build_directories(self) -> None: 535 """Set up necessary build directories.""" 536 # Clean and create directories 537 self.clean_directory(self.build_dir) 538 539 self.create_directory(self.build_dir) 540 self.create_directory(self.build_runtime_src_dir) 541 self.create_directory(self.build_generated_src_dir) 542 self.create_directory(self.build_author_src_dir) 543 self.create_directory(self.build_generated_dir) 544 self.create_directory(self.build_user_dir) 545 546 def prepare_panda_vm(self): 547 """Download and extract Panda VM.""" 548 self.create_directory(self.config.panda_extract_dir) 549 550 url = self.config.panda_url 551 filename = url.split("/")[-1] 552 target_file = self.config.panda_extract_dir / filename 553 version = Path(filename).stem # Use the filename without extension as version 554 555 if not self.check_local_version(version): 556 self.clean_directory(self.config.panda_package_dir) 557 self.logger.info("Downloading panda VM version: %s", version) 558 self.download_file(target_file, url, self.config.panda_userinfo) 559 self.extract_file(target_file, self.config.panda_extract_dir) 560 self.write_local_version(version) 561 self.logger.info("Completed download and extraction.") 562 563 def check_local_version(self, version: str) -> bool: 564 """Check if the local version matches the desired version.""" 565 if not self.config.panda_version_file.exists(): 566 return False 567 try: 568 with open(self.config.panda_version_file) as vf: 569 local_version = vf.read().strip() 570 return local_version == version 571 except OSError as e: 572 self.logger.warning("Failed to read version file: %s", e) 573 return False 574 575 def write_local_version(self, version: str) -> None: 576 """Write the local version to the version file.""" 577 try: 578 with open(self.config.panda_version_file, "w") as vf: 579 vf.write(version) 580 except OSError as e: 581 self.logger.warning("Failed to write version file: %s", e) 582 583 def compile( 584 self, 585 output_dir: Path, 586 input_files: Iterable[Path], 587 include_dirs: Sequence[Path] = (), 588 compile_flags: Sequence[str] = (), 589 ) -> list[Path]: 590 """Compile source files.""" 591 output_files: list[Path] = [] 592 593 for input_file in input_files: 594 name = input_file.name 595 output_file = output_dir / f"{name}.o" 596 597 if name.endswith(".c"): 598 compiler = self.config.cc 599 std = "gnu11" 600 else: 601 compiler = self.config.cxx 602 std = "gnu++17" 603 604 command = [ 605 compiler, 606 "-c", 607 "-fvisibility=hidden", 608 "-fPIC", 609 # "-Wall", 610 # "-Wextra", 611 f"-std={std}", 612 "-o", 613 output_file, 614 input_file, 615 *compile_flags, 616 ] 617 618 for include_dir in include_dirs: 619 if include_dir.exists(): # Only include directories that exist 620 command.append(f"-I{include_dir}") 621 622 self.run_command(command) 623 624 output_files.append(output_file) 625 626 return output_files 627 628 def link( 629 self, 630 output_file: Path, 631 input_files: Sequence[Path], 632 shared: bool = False, 633 link_options: Sequence[str] = (), 634 ) -> None: 635 """Link object files.""" 636 if len(input_files) == 0: 637 self.logger.warning("No input files to link") 638 return 639 640 command = [ 641 self.config.cxx, 642 "-fPIC", 643 "-o", 644 output_file, 645 *input_files, 646 *link_options, 647 ] 648 649 if shared: 650 command.append("-shared") 651 652 self.run_command(command) 653 654 def run( 655 self, 656 target: Path, 657 ld_lib_path: Path, 658 args: Sequence[str] = (), 659 ) -> float: 660 """Run the compiled target.""" 661 command = [ 662 target, 663 *args, 664 ] 665 666 return self.run_command( 667 command, 668 env={"LD_LIBRARY_PATH": ld_lib_path}, 669 ) 670 671 def create_arktsconfig( 672 self, 673 arktsconfig_file: Path, 674 app_paths: Mapping[str, Path] | None = None, 675 ) -> None: 676 """Create ArkTS configuration file.""" 677 paths = { 678 "std": self.config.panda_ets_dir / "stdlib/std", 679 "escompat": self.config.panda_ets_dir / "stdlib/escompat", 680 } 681 682 if app_paths is not None: 683 paths.update(app_paths) 684 685 config_content = { 686 "compilerOptions": { 687 "baseUrl": str(self.config.panda_tool_dir), 688 "paths": {key: [str(value)] for key, value in paths.items()}, 689 } 690 } 691 692 with open(arktsconfig_file, "w") as json_file: 693 json.dump(config_content, json_file, indent=2) 694 695 self.logger.debug("Created configuration file at: %s", arktsconfig_file) 696 697 def compile_abc( 698 self, 699 output_dir: Path, 700 input_files: Iterable[Path], 701 arktsconfig_file: Path, 702 ) -> list[Path]: 703 """Compile ETS files to ABC format.""" 704 output_files: list[Path] = [] 705 706 for input_file in input_files: 707 name = input_file.name 708 output_file = output_dir / f"{name}.abc" 709 output_dump = output_dir / f"{name}.abc.dump" 710 711 es2panda_path = self.config.panda_tool_dir / "bin/es2panda" 712 713 gen_abc_command = [ 714 es2panda_path, 715 input_file, 716 "--output", 717 output_file, 718 "--extension", 719 "ets", 720 "--arktsconfig", 721 arktsconfig_file, 722 ] 723 724 self.run_command(gen_abc_command) 725 726 output_files.append(output_file) 727 728 ark_disasm_path = self.config.panda_tool_dir / "bin/ark_disasm" 729 if not ark_disasm_path.exists(): 730 self.logger.warning( 731 "ark_disasm not found at %s, skipping disassembly", ark_disasm_path 732 ) 733 continue 734 735 gen_abc_dump_command = [ 736 ark_disasm_path, 737 output_file, 738 output_dump, 739 ] 740 741 self.run_command(gen_abc_dump_command) 742 743 return output_files 744 745 def link_abc( 746 self, 747 target: Path, 748 input_files: Sequence[Path], 749 ) -> None: 750 """Link ABC files.""" 751 if len(input_files) == 0: 752 self.logger.warning("No input files to link") 753 return 754 755 ark_link_path = self.config.panda_tool_dir / "bin/ark_link" 756 757 command = [ 758 ark_link_path, 759 "--output", 760 target, 761 "--", 762 *input_files, 763 ] 764 765 self.run_command(command) 766 767 def run_abc( 768 self, 769 abc_target: Path, 770 ld_lib_path: Path, 771 entry: str, 772 ) -> float: 773 """Run the compiled ABC file with the Ark runtime.""" 774 ark_path = self.config.panda_tool_dir / "bin/ark" 775 776 etsstdlib_path = self.config.panda_ets_dir / "etsstdlib.abc" 777 778 command = [ 779 ark_path, 780 f"--boot-panda-files={etsstdlib_path}", 781 f"--load-runtimes=ets", 782 abc_target, 783 entry, 784 ] 785 786 return self.run_command( 787 command, 788 env={"LD_LIBRARY_PATH": ld_lib_path}, 789 ) 790 791 792class RepositoryUpgrader(BuildUtils): 793 """Upgrade the code from a specified URL.""" 794 795 def __init__( 796 self, 797 repo_url: str, 798 config: BuildConfig, 799 verbosity: int = logging.INFO, 800 ): 801 super().__init__(verbosity) 802 self.config = config 803 804 self.repo_url = repo_url 805 806 def fetch_and_upgrade(self): 807 filename = self.repo_url.split("/")[-1] 808 version = self.repo_url.split("/")[-2] 809 810 extract_dir = self.config.locator.root_dir / "../tmp" 811 target_file = extract_dir / filename 812 self.create_directory(extract_dir) 813 self.download_file(target_file, self.repo_url) 814 self.extract_file(target_file, extract_dir) 815 816 tmp_taihe_pkg_dir = extract_dir / "taihe" 817 self.clean_directory(self.config.locator.root_dir) 818 self.move_directory(tmp_taihe_pkg_dir, self.config.locator.root_dir) 819 self.clean_directory(extract_dir) 820 821 self.logger.info("Successfully upgraded code to version %s", version) 822 823 824class TaiheTryitParser(argparse.ArgumentParser): 825 """Parser for the Taihe Tryit CLI.""" 826 827 def register_common_configs(self) -> None: 828 self.add_argument( 829 "-v", 830 "--verbose", 831 action="count", 832 default=0, 833 help="Increase verbosity (can be used multiple times)", 834 ) 835 836 def register_project_configs(self) -> None: 837 self.add_argument( 838 "target_directory", 839 type=str, 840 help="The target directory containing source files for the project", 841 ) 842 self.add_argument( 843 "-u", 844 "--user", 845 type=UserType, 846 choices=list(UserType), 847 required=True, 848 help="User type for the build system (ani/cpp)", 849 ) 850 851 def register_build_configs(self) -> None: 852 self.add_argument( 853 "-O", 854 "--optimization", 855 type=str, 856 nargs="?", 857 default="0", 858 const="0", 859 help="Optimization level for compilation (0-3)", 860 ) 861 862 def register_generate_configs(self) -> None: 863 self.add_argument( 864 "--sts-keep-name", 865 action="store_true", 866 help="Keep original function and interface method names", 867 ) 868 self.add_argument( 869 "--cmake", 870 action="store_true", 871 help="Generate CMake files for the project", 872 ) 873 874 def register_update_configs(self) -> None: 875 self.add_argument( 876 "URL", 877 type=str, 878 help="The URL to fetch the code from", 879 ) 880 881 882def main(config: BuildConfig | None = None): 883 parser = TaiheTryitParser( 884 prog="taihe-tryit", 885 description="Build and run project from a target directory", 886 ) 887 parser.register_common_configs() 888 889 subparsers = parser.add_subparsers(dest="command", required=True) 890 891 parser_create = subparsers.add_parser( 892 "create", 893 help="Create a simple example", 894 ) 895 parser_create.register_common_configs() 896 parser_create.register_project_configs() 897 898 parser_generate = subparsers.add_parser( 899 "generate", 900 help="Generate code from the target directory", 901 ) 902 parser_generate.register_common_configs() 903 parser_generate.register_project_configs() 904 parser_generate.register_generate_configs() 905 906 parser_build = subparsers.add_parser( 907 "build", 908 help="Build the project from the target directory", 909 ) 910 parser_build.register_common_configs() 911 parser_build.register_project_configs() 912 parser_build.register_build_configs() 913 914 parser_test = subparsers.add_parser( 915 "test", 916 help="Generate and build the project from the target directory", 917 ) 918 parser_test.register_common_configs() 919 parser_test.register_project_configs() 920 parser_test.register_build_configs() 921 parser_test.register_generate_configs() 922 923 parser_upgrade = subparsers.add_parser( 924 "upgrade", 925 help="Upgrade using the specified URL", 926 ) 927 parser_upgrade.register_common_configs() 928 parser_upgrade.register_update_configs() 929 930 args = parser.parse_args() 931 932 match args.verbose: 933 case 0: 934 verbosity = logging.INFO 935 case 1: 936 verbosity = logging.DEBUG 937 case 2: 938 verbosity = TRACE_CONCISE 939 case _: 940 verbosity = TRACE_VERBOSE 941 942 if config is None: 943 config = BuildConfig() 944 945 try: 946 if args.command == "upgrade": 947 upgrader = RepositoryUpgrader( 948 args.URL, 949 config=config, 950 verbosity=verbosity, 951 ) 952 upgrader.fetch_and_upgrade() 953 else: 954 build_system = BuildSystem( 955 args.target_directory, 956 args.user, 957 config=config, 958 verbosity=verbosity, 959 ) 960 if args.command == "create": 961 build_system.create() 962 if args.command in ("generate", "test"): 963 build_system.generate( 964 sts_keep_name=args.sts_keep_name, 965 cmake=args.cmake, 966 ) 967 if args.command in ("build", "test"): 968 build_system.build( 969 opt_level=args.optimization, 970 ) 971 except KeyboardInterrupt: 972 print("\nInterrupted. Exiting build process.", file=sys.stderr) 973 sys.exit(1) 974 except Exception as e: 975 print(f"Error: {e}", file=sys.stderr) 976 sys.exit(1) 977 978 979if __name__ == "__main__": 980 main()