1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4# 5# Copyright (c) 2025 Northeastern University 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19import argparse 20import os 21import shutil 22import subprocess 23import sys 24from datetime import datetime 25from pathlib import Path 26 27sys.path.append( 28 os.path.dirname(os.path.dirname(os.path.dirname( 29 os.path.abspath(__file__))))) 30 31from ohos.sbom.common.utils import write_json 32from ohos.sbom.converters.api import SBOMConverter 33from ohos.sbom.converters.base import SBOMFormat 34from ohos.sbom.extraction.local_resource_loader import LocalResourceLoader 35from ohos.sbom.pipeline.sbom_generator import SBOMGenerator 36 37 38def generate_manifest(args): 39 """ 40 Generate a release manifest using 'python .repo/repo/repo manifest -r -o' command. 41 42 The manifest is saved to: 43 <out_dir>/sbom/manifests/manifest_tag_YYYYMMDD_HHMMSS.xml 44 45 Args: 46 args: Parsed command-line arguments with output 47 """ 48 49 source_root = Path(args.source_root_dir).resolve() 50 out_dir = Path(args.out_dir).resolve() 51 52 repo_script = source_root / ".repo" / "repo" / "repo" 53 if not repo_script.exists(): 54 raise FileNotFoundError(f"Repo script not found: {repo_script}\n" 55 f"Please make sure you are in a valid repo workspace.") 56 57 manifest_dir = out_dir / "sbom" / "manifests" 58 manifest_dir.mkdir(parents=True, exist_ok=True) 59 60 timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S") 61 output_path = manifest_dir / f"manifest_tag_{timestamp_str}.xml" 62 63 cmd = [ 64 "python", str(repo_script), 65 "manifest", "-r", "-o", str(output_path) 66 ] 67 68 try: 69 print(f"[INFO] Generating manifest: {output_path}") 70 print(f"[DEBUG] Running command: {' '.join(cmd)}") 71 72 subprocess.run(cmd, check=True, cwd=source_root) 73 print(f"[INFO] Manifest generated successfully: {output_path}") 74 return output_path 75 76 except subprocess.CalledProcessError as e: 77 print(f"[ERROR] Failed to generate manifest (command exited with error): {e}") 78 raise 79 except FileNotFoundError: 80 print(f"[ERROR] Python interpreter not found or repo script missing.") 81 raise 82 except Exception as e: 83 print(f"[ERROR] Unexpected error during manifest generation: {e}") 84 raise 85 86 87def set_path(args): 88 LocalResourceLoader.set_source_root(args.source_root_dir) 89 LocalResourceLoader.set_out_root(args.out_dir) 90 91 92def generate_sbom(args): 93 """ 94 Generate SBOM (Software Bill of Materials) and clean up temporary files afterward. 95 """ 96 # Define the output directory for SBOM artifacts 97 sbom_dir = os.path.join(args.out_dir, "sbom") 98 os.makedirs(sbom_dir, exist_ok=True) 99 100 # Paths to temporary files/directories to be cleaned up 101 manifests_dir = os.path.join(sbom_dir, "manifests") 102 gn_gen_file = os.path.join(sbom_dir, "gn_gen.json") 103 104 try: 105 # Generate SBOM metadata using the provided arguments 106 sbom_meta_data = SBOMGenerator(args).build_sbom() 107 108 # Convert SBOM metadata to SPDX format 109 spdx_data = SBOMConverter(sbom_meta_data).convert(SBOMFormat.SPDX) 110 111 # Define output file paths 112 output_file_meta_data = os.path.join(sbom_dir, "sbom_meta_data.json") 113 output_file_spdx = os.path.join(sbom_dir, "spdx.json") 114 115 # Write SBOM metadata and SPDX data to JSON files 116 write_json(sbom_meta_data.to_dict(), output_file_meta_data) 117 write_json(spdx_data, output_file_spdx) 118 119 finally: 120 # Ensure cleanup runs regardless of success or failure 121 122 # Remove the 'manifest' directory if it exists 123 if os.path.exists(manifests_dir) and os.path.isdir(manifests_dir): 124 try: 125 shutil.rmtree(manifests_dir) 126 print(f"Cleaned up directory: {manifests_dir}") 127 except Exception as e: 128 print(f"Failed to delete directory {manifests_dir}: {e}") 129 130 # Remove the 'gn_gen.json' file if it exists 131 if os.path.exists(gn_gen_file) and os.path.isfile(gn_gen_file): 132 try: 133 os.remove(gn_gen_file) 134 print(f"Cleaned up file: {gn_gen_file}") 135 except Exception as e: 136 print(f"Failed to delete file {gn_gen_file}: {e}") 137 138 139def main(): 140 parser = argparse.ArgumentParser() 141 parser.add_argument("--source-root-dir", type=str, required=True, help="project source root directory") 142 parser.add_argument("--out-dir", type=str, required=True, help="SBOM output directory") 143 parser.add_argument("--product", type=str, required=True, help="Product name") 144 parser.add_argument("--platform", type=str, required=True, help="Target platform") 145 args = parser.parse_args() 146 set_path(args) 147 generate_manifest(args) 148 generate_sbom(args) 149 150 return 0 151 152 153if __name__ == '__main__': 154 sys.exit(main()) 155