/ scripts / add_license_header.py
add_license_header.py
  1  #!/usr/bin/env python3
  2  """
  3  Batch add Apache 2.0 license header to .go and .py source files.
  4  Skips files that already contain the license header.
  5  """
  6  
  7  import os
  8  import sys
  9  
 10  GO_HEADER = """\
 11  // Copyright (c) 2024-2026 Tencent Zhuque Lab. All rights reserved.
 12  //
 13  // Licensed under the Apache License, Version 2.0 (the "License");
 14  // you may not use this file except in compliance with the License.
 15  // You may obtain a copy of the License at
 16  //
 17  //     http://www.apache.org/licenses/LICENSE-2.0
 18  //
 19  // Unless required by applicable law or agreed to in writing, software
 20  // distributed under the License is distributed on an "AS IS" BASIS,
 21  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 22  // See the License for the specific language governing permissions and
 23  // limitations under the License.
 24  //
 25  // Requirement: Any integration or derivative work must explicitly attribute
 26  // Tencent Zhuque Lab (https://github.com/Tencent/AI-Infra-Guard) in its
 27  // documentation or user interface, as detailed in the NOTICE file.
 28  
 29  """
 30  
 31  PY_HEADER = """\
 32  # Copyright (c) 2024-2026 Tencent Zhuque Lab. All rights reserved.
 33  #
 34  # Licensed under the Apache License, Version 2.0 (the "License");
 35  # you may not use this file except in compliance with the License.
 36  # You may obtain a copy of the License at
 37  #
 38  #     http://www.apache.org/licenses/LICENSE-2.0
 39  #
 40  # Unless required by applicable law or agreed to in writing, software
 41  # distributed under the License is distributed on an "AS IS" BASIS,
 42  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 43  # See the License for the specific language governing permissions and
 44  # limitations under the License.
 45  #
 46  # Requirement: Any integration or derivative work must explicitly attribute
 47  # Tencent Zhuque Lab (https://github.com/Tencent/AI-Infra-Guard) in its
 48  # documentation or user interface, as detailed in the NOTICE file.
 49  
 50  """
 51  
 52  SKIP_MARKER = "Copyright (c) 2024-2026 Tencent Zhuque Lab"
 53  
 54  SKIP_DIRS = {".git", "vendor", "node_modules", "__pycache__", ".mypy_cache"}
 55  
 56  def should_skip_dir(path):
 57      parts = path.replace("\\", "/").split("/")
 58      return any(p in SKIP_DIRS for p in parts)
 59  
 60  def process_file(filepath, header, dry_run=False):
 61      with open(filepath, "r", encoding="utf-8", errors="replace") as f:
 62          content = f.read()
 63  
 64      if SKIP_MARKER in content:
 65          return "skip"
 66  
 67      # For .py files: preserve shebang line if present
 68      if filepath.endswith(".py") and content.startswith("#!"):
 69          shebang_end = content.find("\n") + 1
 70          new_content = content[:shebang_end] + header + content[shebang_end:]
 71      else:
 72          new_content = header + content
 73  
 74      if not dry_run:
 75          with open(filepath, "w", encoding="utf-8") as f:
 76              f.write(new_content)
 77      return "added"
 78  
 79  def main():
 80      dry_run = "--dry-run" in sys.argv
 81      root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 82  
 83      added = []
 84      skipped = []
 85      errors = []
 86  
 87      for dirpath, dirnames, filenames in os.walk(root):
 88          # Prune skip dirs in-place
 89          dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
 90  
 91          rel_dir = os.path.relpath(dirpath, root)
 92  
 93          for filename in filenames:
 94              filepath = os.path.join(dirpath, filename)
 95              rel_path = os.path.relpath(filepath, root)
 96  
 97              if filename.endswith(".go"):
 98                  header = GO_HEADER
 99              elif filename.endswith(".py"):
100                  header = PY_HEADER
101              else:
102                  continue
103  
104              try:
105                  result = process_file(filepath, header, dry_run=dry_run)
106                  if result == "added":
107                      added.append(rel_path)
108                      print(f"  [added]  {rel_path}")
109                  else:
110                      skipped.append(rel_path)
111              except Exception as e:
112                  errors.append((rel_path, str(e)))
113                  print(f"  [error]  {rel_path}: {e}", file=sys.stderr)
114  
115      print(f"\n{'[DRY RUN] ' if dry_run else ''}Done.")
116      print(f"  Added  : {len(added)}")
117      print(f"  Skipped: {len(skipped)}")
118      print(f"  Errors : {len(errors)}")
119  
120  if __name__ == "__main__":
121      main()