1#!/usr/bin/env python3 2# 3# Copyright (C) 2020 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17import sys 18import os 19import argparse 20from datetime import date 21import subprocess 22from enum import Enum 23from textwrap import dedent 24from shutil import rmtree 25from shutil import copyfile 26from shutil import copytree 27import re 28 29try: 30 # non-default python3 module, be helpful if it is missing 31 import toml 32except ModuleNotFoundError as e: 33 print(e) 34 print("Consider running `pip install toml` to install this module") 35 exit(-1) 36 37# cd into directory of script 38os.chdir(os.path.dirname(os.path.abspath(__file__))) 39 40FRAMEWORKS_SUPPORT_FP = os.path.abspath(os.path.join(os.getcwd(), '..', '..')) 41SAMPLE_OWNERS_FP = os.path.abspath(os.path.join(os.getcwd(), 'kotlin-template', 'groupId', 'OWNERS')) 42SAMPLE_JAVA_SRC_FP = os.path.abspath(os.path.join(os.getcwd(), 'java-template', 'groupId', 'artifactId')) 43SAMPLE_KOTLIN_SRC_FP = os.path.abspath(os.path.join(os.getcwd(), 'kotlin-template', 'groupId', 'artifactId')) 44SAMPLE_COMPOSE_SRC_FP = os.path.abspath(os.path.join(os.getcwd(), 'compose-template', 'groupId', 'artifactId')) 45NATIVE_SRC_FP = os.path.abspath(os.path.join(os.getcwd(), 'native-template', 'groupId', 'artifactId')) 46SETTINGS_GRADLE_FP = os.path.abspath(os.path.join(os.getcwd(), '..', '..', "settings.gradle")) 47LIBRARY_VERSIONS_REL = './libraryversions.toml' 48LIBRARY_VERSIONS_FP = os.path.join(FRAMEWORKS_SUPPORT_FP, LIBRARY_VERSIONS_REL) 49DOCS_TOT_BUILD_GRADLE_REL = './docs-tip-of-tree/build.gradle' 50DOCS_TOT_BUILD_GRADLE_FP = os.path.join(FRAMEWORKS_SUPPORT_FP, DOCS_TOT_BUILD_GRADLE_REL) 51 52# Set up input arguments 53parser = argparse.ArgumentParser( 54 description=("""Genereates new project in androidx.""")) 55parser.add_argument( 56 'group_id', 57 help='group_id for the new library') 58parser.add_argument( 59 'artifact_id', 60 help='artifact_id for the new library') 61 62 63class ProjectType(Enum): 64 KOTLIN = 0 65 JAVA = 1 66 NATIVE = 2 67 68def print_e(*args, **kwargs): 69 print(*args, file=sys.stderr, **kwargs) 70 71def cp(src_path_dir, dst_path_dir): 72 """Copies all files in the src_path_dir into the dst_path_dir 73 74 Args: 75 src_path_dir: the source directory, which must exist 76 dst_path_dir: the distination directory 77 """ 78 if not os.path.exists(dst_path_dir): 79 os.makedirs(dst_path_dir) 80 if not os.path.exists(src_path_dir): 81 print_e('cp error: Source path %s does not exist.' % src_path_dir) 82 return None 83 try: 84 copytree(src_path_dir, dst_path_dir, dirs_exist_ok=True) 85 except Error as err: 86 print_e('FAIL: Unable to copy %s to destination %s' % (src_path_dir, dst_path_dir)) 87 return None 88 return dst_path_dir 89 90def rm(path): 91 if os.path.isdir(path): 92 rmtree(path) 93 elif os.path.exists(path): 94 os.remove(path) 95 96def mv_dir(src_path_dir, dst_path_dir): 97 """Moves a directory from src_path_dir to dst_path_dir. 98 99 Args: 100 src_path_dir: the source directory, which must exist 101 dst_path_dir: the distination directory 102 """ 103 if os.path.exists(dst_path_dir): 104 print_e('rename error: Destination path %s already exists.' % dst_path_dir) 105 return None 106 # If moving to a new parent directory, create that directory 107 parent_dst_path_dir = os.path.dirname(dst_path_dir) 108 if not os.path.exists(parent_dst_path_dir): 109 os.makedirs(parent_dst_path_dir) 110 if not os.path.exists(src_path_dir): 111 print_e('mv error: Source path %s does not exist.' % src_path_dir) 112 return None 113 try: 114 os.rename(src_path_dir, dst_path_dir) 115 except OSError as error: 116 print_e('FAIL: Unable to copy %s to destination %s' % (src_path_dir, dst_path_dir)) 117 print_e(error) 118 return None 119 return dst_path_dir 120 121def rename_file(src_file, new_file_name): 122 """Renames a file from src_file to new_file_name, within the same directory. 123 124 Args: 125 src_file: the source file, which must exist 126 new_file_name: the new file name 127 """ 128 if not os.path.exists(src_file): 129 print_e('mv file error: Source file %s does not exist.' % src_file) 130 return None 131 # Check that destination directory already exists 132 parent_src_file_dir = os.path.dirname(src_file) 133 new_file_path = os.path.join(parent_src_file_dir, new_file_name) 134 if os.path.exists(new_file_path): 135 print_e('mv file error: Source file %s already exists.' % new_file_path) 136 return None 137 try: 138 os.rename(src_file, new_file_path) 139 except OSError as error: 140 print_e('FAIL: Unable to rename %s to destination %s' % (src_file, new_file_path)) 141 print_e(error) 142 return None 143 return new_file_path 144 145def create_file(path): 146 """ 147 Creates an empty file if it does not already exist. 148 """ 149 open(path, "a").close() 150 151def generate_package_name(group_id, artifact_id): 152 final_group_id_word = group_id.split(".")[-1] 153 artifact_id_suffix = re.sub(r"\b%s\b" % final_group_id_word, "", artifact_id) 154 artifact_id_suffix = artifact_id_suffix.replace("-", ".") 155 if (final_group_id_word == artifact_id): 156 return group_id + artifact_id_suffix 157 elif (final_group_id_word != artifact_id): 158 if ("." in artifact_id_suffix): 159 return group_id + artifact_id_suffix 160 else: 161 return group_id + "." + artifact_id_suffix 162 163def validate_name(group_id, artifact_id): 164 if not group_id.startswith("androidx."): 165 print_e("Group ID must start with androidx.") 166 return False 167 final_group_id_word = group_id.split(".")[-1] 168 if not artifact_id.startswith(final_group_id_word): 169 print_e("Artifact ID must use the final word in the group Id " + \ 170 "as the prefix. For example, `androidx.foo.bar:bar-qux`" + \ 171 "or `androidx.foo:foo-bar` are valid names.") 172 return False 173 return True 174 175def get_year(): 176 return str(date.today().year) 177 178def get_group_id_version_macro(group_id): 179 group_id_version_macro = group_id.replace("androidx.", "").replace(".", "_").upper() 180 if group_id == "androidx.compose": 181 group_id_version_macro = "COMPOSE" 182 elif group_id.startswith("androidx.compose"): 183 group_id_version_macro = group_id.replace("androidx.compose.", "").replace(".", 184 "_").upper() 185 return group_id_version_macro 186 187def sed(before, after, file): 188 with open(file) as f: 189 file_contents = f.read() 190 new_file_contents = file_contents.replace(before, after) 191 # write back the file 192 with open(file,"w") as f: 193 f.write(new_file_contents) 194 195def remove_line(line_to_remove, file): 196 with open(file) as f: 197 file_contents = f.readlines() 198 new_file_contents = [] 199 for line in file_contents: 200 if line_to_remove not in line: 201 new_file_contents.append(line) 202 # write back the file 203 with open(file,"w") as f: 204 f.write("".join(new_file_contents)) 205 206def ask_yes_or_no(question): 207 while(True): 208 reply = str(input(question+' (y/n): ')).lower().strip() 209 if reply: 210 if reply[0] == 'y': return True 211 if reply[0] == 'n': return False 212 print("Please respond with y/n") 213 214def ask_project_type(): 215 """Asks the user which type of project they wish to create""" 216 message = dedent(""" 217 Please choose the type of project you would like to create: 218 1: Kotlin (AAR) 219 2: Java (AAR / JAR) 220 3: Native (AAR) 221 """).strip() 222 while(True): 223 reply = str(input(message + "\n")).strip() 224 if reply == "1": return ProjectType.KOTLIN 225 if reply == "2": 226 if confirm_java_project_type(): 227 return ProjectType.JAVA 228 if reply == "3": return ProjectType.NATIVE 229 print("Please respond with one of the presented options") 230 231def confirm_java_project_type(): 232 return ask_yes_or_no("All new androidx projects are expected and encouraged " 233 "to use Kotlin. Java projects should only be used if " 234 "there is a business need to do so. " 235 "Please ack to proceed:") 236 237def ask_library_purpose(): 238 question = ("Project description (please complete the sentence): " 239 "This library makes it easy for developers to... ") 240 while(True): 241 reply = str(input(question)).strip() 242 if reply: return reply 243 print("Please input a description!") 244 245def ask_project_description(): 246 question = ("Please provide a project description: ") 247 while(True): 248 reply = str(input(question)).strip() 249 if reply: return reply 250 print("Please input a description!") 251 252def get_gradle_project_coordinates(group_id, artifact_id): 253 coordinates = group_id.replace("androidx", "").replace(".",":") 254 coordinates += ":" + artifact_id 255 return coordinates 256 257def run_update_api(group_id, artifact_id): 258 gradle_coordinates = get_gradle_project_coordinates(group_id, artifact_id) 259 gradle_cmd = "cd " + FRAMEWORKS_SUPPORT_FP + " && ./gradlew " + gradle_coordinates + ":updateApi" 260 try: 261 subprocess.check_output(gradle_cmd, stderr=subprocess.STDOUT, shell=True) 262 except subprocess.CalledProcessError: 263 print_e('FAIL: Unable run updateApi with command: %s' % gradle_cmd) 264 return None 265 return True 266 267def get_library_type(artifact_id): 268 """Returns the appropriate androidx.build.SoftwareType for the project. 269 """ 270 if "sample" in artifact_id: 271 library_type = "SAMPLES" 272 elif "compiler" in artifact_id: 273 library_type = "ANNOTATION_PROCESSOR" 274 elif "lint" in artifact_id: 275 library_type = "LINT" 276 elif "inspection" in artifact_id: 277 library_type = "IDE_PLUGIN" 278 else: 279 library_type = "PUBLISHED_LIBRARY" 280 return library_type 281 282def get_group_id_path(group_id): 283 """Generates the group ID filepath 284 285 Given androidx.foo.bar, the structure will be: 286 frameworks/support/foo/bar 287 288 Args: 289 group_id: group_id of the new library 290 """ 291 return FRAMEWORKS_SUPPORT_FP + "/" + group_id.replace("androidx.", "").replace(".", "/") 292 293def get_full_artifact_path(group_id, artifact_id): 294 """Generates the full artifact ID filepath 295 296 Given androidx.foo.bar:bar-qux, the structure will be: 297 frameworks/support/foo/bar/bar-qux 298 299 Args: 300 group_id: group_id of the new library 301 artifact_id: group_id of the new library 302 """ 303 group_id_path = get_group_id_path(group_id) 304 return group_id_path + "/" + artifact_id 305 306def get_package_documentation_file_dir(group_id, artifact_id): 307 """Generates the full package documentation directory 308 309 Given androidx.foo.bar:bar-qux, the structure will be: 310 frameworks/support/foo/bar/bar-qux/src/main/java/androidx/foo/package-info.java 311 312 For Kotlin: 313 frameworks/support/foo/bar/bar-qux/src/main/java/androidx/foo/<group>-<artifact>-documentation.md 314 315 For Compose: 316 frameworks/support/foo/bar/bar-qux/src/commonMain/kotlin/androidx/foo/<group>-<artifact>-documentation.md 317 318 Args: 319 group_id: group_id of the new library 320 artifact_id: group_id of the new library 321 """ 322 full_artifact_path = get_full_artifact_path(group_id, artifact_id) 323 if "compose" in group_id: 324 group_id_subpath = "/src/commonMain/kotlin/" + \ 325 group_id.replace(".", "/") 326 else: 327 group_id_subpath = "/src/main/java/" + \ 328 group_id.replace(".", "/") 329 return full_artifact_path + group_id_subpath 330 331def get_package_documentation_filename(group_id, artifact_id, project_type): 332 """Generates the documentation filename 333 334 Given androidx.foo.bar:bar-qux, the structure will be: 335 package-info.java 336 337 or for Kotlin: 338 <group>-<artifact>-documentation.md 339 340 Args: 341 group_id: group_id of the new library 342 artifact_id: group_id of the new library 343 is_kotlin_project: whether or not the library is a kotin project 344 """ 345 if project_type == ProjectType.JAVA: 346 return "package-info.java" 347 else: 348 formatted_group_id = group_id.replace(".", "-") 349 return "%s-%s-documentation.md" % (formatted_group_id, artifact_id) 350 351def is_compose_project(group_id, artifact_id): 352 """Returns true if project can be inferred to be a compose / Kotlin project 353 """ 354 return "compose" in group_id or "compose" in artifact_id 355 356def create_directories(group_id, artifact_id, project_type, is_compose_project): 357 """Creates the standard directories for the given group_id and artifact_id. 358 359 Given androidx.foo.bar:bar-qux, the structure will be: 360 frameworks/support/foo/bar/bar-qux/build.gradle 361 frameworks/support/foo/bar/bar-qux/src/main/java/androidx/foo/bar/package-info.java 362 frameworks/support/foo/bar/bar-qux/src/main/java/androidx/foo/bar/artifact-documentation.md 363 frameworks/support/foo/bar/bar-qux/api/current.txt 364 365 Args: 366 group_id: group_id of the new library 367 artifact_id: group_id of the new library 368 """ 369 full_artifact_path = get_full_artifact_path(group_id, artifact_id) 370 if not os.path.exists(full_artifact_path): 371 os.makedirs(full_artifact_path) 372 373 # Copy over the OWNERS file if it doesn't exit 374 group_id_path = get_group_id_path(group_id) 375 if not os.path.exists(group_id_path + "/OWNERS"): 376 copyfile(SAMPLE_OWNERS_FP, group_id_path + "/OWNERS") 377 378 # Copy the full src structure, depending on the project source code 379 if is_compose_project: 380 print("Auto-detected Compose project.") 381 cp(SAMPLE_COMPOSE_SRC_FP, full_artifact_path) 382 elif project_type == ProjectType.NATIVE: 383 cp(NATIVE_SRC_FP, full_artifact_path) 384 elif project_type == ProjectType.KOTLIN: 385 cp(SAMPLE_KOTLIN_SRC_FP, full_artifact_path) 386 else: 387 cp(SAMPLE_JAVA_SRC_FP, full_artifact_path) 388 389 # Java only libraries have no dependency on android. 390 # Java-only produces a jar, whereas an android library produces an aar. 391 if (project_type == ProjectType.JAVA and 392 (get_library_type(artifact_id) == "LINT" or 393 ask_yes_or_no("Is this a java-only library? Java-only libraries produce" 394 " JARs, whereas Android libraries produce AARs."))): 395 sed("com.android.library", "java-library", 396 full_artifact_path + "/build.gradle") 397 sed("org.jetbrains.kotlin.android", "kotlin", 398 full_artifact_path + "/build.gradle") 399 400 # Atomic group Ids have their version configured automatically, 401 # so we can remove the version line from the build file. 402 if is_group_id_atomic(group_id): 403 remove_line("mavenVersion = LibraryVersions.", 404 full_artifact_path + "/build.gradle") 405 406 # If the project is a library that produces a jar/aar that will go 407 # on GMaven, ask for a special project description. 408 if get_library_type(artifact_id) == "PUBLISHED_LIBRARY": 409 project_description = ask_library_purpose() 410 else: 411 project_description = ask_project_description() 412 413 # Set up the package documentation. 414 full_package_docs_dir = get_package_documentation_file_dir(group_id, artifact_id) 415 package_docs_filename = get_package_documentation_filename(group_id, artifact_id, project_type) 416 full_package_docs_file = os.path.join(full_package_docs_dir, package_docs_filename) 417 # Compose projects use multiple main directories, so we handle it separately 418 if is_compose_project: 419 # Kotlin projects use -documentation.md files, so we need to rename it appropriately. 420 rename_file(full_artifact_path + "/src/commonMain/kotlin/groupId/artifactId-documentation.md", 421 package_docs_filename) 422 mv_dir(full_artifact_path + "/src/commonMain/kotlin/groupId", full_package_docs_dir) 423 else: 424 if project_type != ProjectType.JAVA: 425 # Kotlin projects use -documentation.md files, so we need to rename it appropriately. 426 # We also rename this file for native projects in case they also have public Kotlin APIs 427 rename_file(full_artifact_path + "/src/main/java/groupId/artifactId-documentation.md", 428 package_docs_filename) 429 mv_dir(full_artifact_path + "/src/main/java/groupId", full_package_docs_dir) 430 431 # Populate the library type 432 library_type = get_library_type(artifact_id) 433 if project_type == ProjectType.NATIVE and library_type == "PUBLISHED_LIBRARY": 434 library_type = "PUBLISHED_NATIVE_LIBRARY" 435 sed("<LIBRARY_TYPE>", library_type, full_artifact_path + "/build.gradle") 436 437 # Populate the YEAR 438 year = get_year() 439 sed("<YEAR>", year, full_artifact_path + "/build.gradle") 440 sed("<YEAR>", year, full_package_docs_file) 441 442 # Populate the PACKAGE 443 package = generate_package_name(group_id, artifact_id) 444 sed("<PACKAGE>", package, full_package_docs_file) 445 sed("<PACKAGE>", package, full_artifact_path + "/build.gradle") 446 447 # Populate the VERSION macro 448 group_id_version_macro = get_group_id_version_macro(group_id) 449 sed("<GROUPID>", group_id_version_macro, full_artifact_path + "/build.gradle") 450 # Update the name and description in the build.gradle 451 sed("<NAME>", group_id + ":" + artifact_id, full_artifact_path + "/build.gradle") 452 if project_type == ProjectType.NATIVE: 453 sed("<NAME>", artifact_id, full_artifact_path + "/src/main/cpp/CMakeLists.txt") 454 sed("<TARGET>", artifact_id, full_artifact_path + "/build.gradle") 455 create_file(full_artifact_path + "/src/main/cpp/" + artifact_id + ".cpp") 456 sed("<DESCRIPTION>", project_description, full_artifact_path + "/build.gradle") 457 458 459def get_new_settings_gradle_line(group_id, artifact_id): 460 """Generates the line needed for frameworks/support/settings.gradle. 461 462 For a library androidx.foo.bar:bar-qux, the new gradle command will be 463 the form: 464 ./gradlew :foo:bar:bar-qux:<command> 465 466 We special case on compose that we can properly populate the build type 467 of either MAIN or COMPOSE. 468 469 Args: 470 group_id: group_id of the new library 471 artifact_id: group_id of the new library 472 """ 473 474 build_type = "MAIN" 475 if is_compose_project(group_id, artifact_id): 476 build_type = "COMPOSE" 477 478 gradle_cmd = get_gradle_project_coordinates(group_id, artifact_id) 479 return "includeProject(\"" + gradle_cmd + "\", [BuildType." + build_type + "])\n" 480 481def update_settings_gradle(group_id, artifact_id): 482 """Updates frameworks/support/settings.gradle with the new library. 483 484 Args: 485 group_id: group_id of the new library 486 artifact_id: group_id of the new library 487 """ 488 # Open file for reading and get all lines 489 with open(SETTINGS_GRADLE_FP, 'r') as f: 490 settings_gradle_lines = f.readlines() 491 num_lines = len(settings_gradle_lines) 492 493 new_settings_gradle_line = get_new_settings_gradle_line(group_id, artifact_id) 494 for i in range(num_lines): 495 cur_line = settings_gradle_lines[i] 496 if "includeProject" not in cur_line: 497 continue 498 # Iterate through until you found the alphabetical place to insert the new line 499 if new_settings_gradle_line <= cur_line: 500 insert_line = i 501 break 502 else: 503 insert_line = i + 1 504 settings_gradle_lines.insert(insert_line, new_settings_gradle_line) 505 506 # Open file for writing and update all lines 507 with open(SETTINGS_GRADLE_FP, 'w') as f: 508 f.writelines(settings_gradle_lines) 509 510def get_new_docs_tip_of_tree_build_grade_line(group_id, artifact_id): 511 """Generates the line needed for docs-tip-of-tree/build.gradle. 512 513 For a library androidx.foo.bar:bar-qux, the new line will be of the form: 514 docs(project(":foo:bar:bar-qux")) 515 516 If it is a sample project, then it will return None. samples(project(":foo:bar:bar-qux-sample")) needs to be added to the androidx block of the library build.gradle file. 517 518 Args: 519 group_id: group_id of the new library 520 artifact_id: group_id of the new library 521 """ 522 523 gradle_cmd = get_gradle_project_coordinates(group_id, artifact_id) 524 prefix = "docs" 525 if "sample" in gradle_cmd: 526 print("Auto-detected sample project. Please add the sample dependency to androidx block of the library build.gradle file. See compose/ui/ui/build.gradle for an example.") 527 return None 528 return " %s(project(\"%s\"))\n" % (prefix, gradle_cmd) 529 530def update_docs_tip_of_tree_build_grade(group_id, artifact_id): 531 """Updates docs-tip-of-tree/build.gradle with the new library. 532 533 We ask for confirmation if the library contains either "benchmark" 534 or "test". 535 536 Args: 537 group_id: group_id of the new library 538 artifact_id: group_id of the new library 539 """ 540 # Confirm with user that we want to generate docs for anything 541 # that might be a test or a benchmark. 542 if ("test" in group_id or "test" in artifact_id 543 or "benchmark" in group_id or "benchmark" in artifact_id): 544 if not ask_yes_or_no(("Should tip-of-tree documentation be generated " 545 "for project %s:%s?" % (group_id, artifact_id))): 546 return 547 548 # Open file for reading and get all lines 549 with open(DOCS_TOT_BUILD_GRADLE_FP, 'r') as f: 550 docs_tot_bg_lines = f.readlines() 551 index_of_real_dependencies_block = next( 552 idx for idx, line in enumerate(docs_tot_bg_lines) if line.startswith("dependencies {") 553 ) 554 if (index_of_real_dependencies_block == None): 555 raise RuntimeError("Couldn't find dependencies block") 556 num_lines = len(docs_tot_bg_lines) 557 558 new_docs_tot_bq_line = get_new_docs_tip_of_tree_build_grade_line(group_id, artifact_id) 559 for i in range(index_of_real_dependencies_block, num_lines): 560 cur_line = docs_tot_bg_lines[i] 561 if "project" not in cur_line: 562 continue 563 # Iterate through until you found the alphabetical place to insert the new line 564 if new_docs_tot_bq_line.split("project")[1] <= cur_line.split("project")[1]: 565 insert_line = i 566 break 567 else: 568 insert_line = i + 1 569 docs_tot_bg_lines.insert(insert_line, new_docs_tot_bq_line) 570 571 # Open file for writing and update all lines 572 with open(DOCS_TOT_BUILD_GRADLE_FP, 'w') as f: 573 f.writelines(docs_tot_bg_lines) 574 575 576def insert_new_group_id_into_library_versions_toml(group_id): 577 """Inserts a group ID into the libraryversions.toml file. 578 579 If one already exists, then this function just returns and reuses 580 the existing one. 581 582 Args: 583 group_id: group_id of the new library 584 """ 585 new_group_id_variable_name = group_id.replace("androidx.","").replace(".","_").upper() 586 587 # Open toml file 588 library_versions = toml.load(LIBRARY_VERSIONS_FP, decoder=toml.TomlPreserveCommentDecoder()) 589 if not new_group_id_variable_name in library_versions["versions"]: 590 library_versions["versions"][new_group_id_variable_name] = "1.0.0-alpha01" 591 if not new_group_id_variable_name in library_versions["groups"]: 592 decoder = toml.decoder.TomlDecoder() 593 group_entry = decoder.get_empty_inline_table() 594 group_entry["group"] = group_id 595 group_entry["atomicGroupVersion"] = "versions." + new_group_id_variable_name 596 library_versions["groups"][new_group_id_variable_name] = group_entry 597 598 # Sort the entries 599 library_versions["versions"] = dict(sorted(library_versions["versions"].items())) 600 library_versions["groups"] = dict(sorted(library_versions["groups"].items())) 601 602 # Open file for writing and update toml 603 with open(LIBRARY_VERSIONS_FP, 'w') as f: 604 # Encoder arg enables preservation of inline dicts. 605 versions_toml_file_string = toml.dumps(library_versions, 606 encoder=toml.TomlPreserveCommentEncoder(preserve=True)) 607 versions_toml_file_string_new = re.sub(",]", " ]", versions_toml_file_string) 608 versions_toml_file_string_new 609 f.write(versions_toml_file_string_new) 610 611 612def is_group_id_atomic(group_id): 613 """Checks if a group ID is atomic using the libraryversions.toml file. 614 615 If one already exists, then this function evaluates the group id 616 and returns the appropriate atomicity. Otherwise, it returns 617 False. 618 619 Example of an atomic library group: 620 ACTIVITY = { group = "androidx.work", atomicGroupVersion = "WORK" } 621 Example of a non-atomic library group: 622 WEAR = { group = "androidx.wear" } 623 624 Args: 625 group_id: group_id of the library we're checking. 626 """ 627 library_versions = toml.load(LIBRARY_VERSIONS_FP) 628 for library_group in library_versions["groups"]: 629 if group_id == library_versions["groups"][library_group]["group"]: 630 return "atomicGroupVersion" in library_versions["groups"][library_group] 631 632 return False 633 634 635def print_todo_list(group_id, artifact_id, project_type): 636 """Prints to the todo list once the script has finished. 637 638 There are some pieces that can not be automated or require human eyes. 639 List out the appropriate todos so that the users knows what needs 640 to be done prior to uploading. 641 642 Args: 643 group_id: group_id of the new library 644 artifact_id: group_id of the new library 645 """ 646 build_gradle_path = get_full_artifact_path(group_id, artifact_id) + \ 647 "/build.gradle" 648 owners_file_path = get_group_id_path(group_id) + "/OWNERS" 649 package_docs_path = os.path.join( 650 get_package_documentation_file_dir(group_id, artifact_id), 651 get_package_documentation_filename(group_id, artifact_id, project_type)) 652 print("---\n") 653 print("Created the project. The following TODOs need to be completed by " 654 "you:\n") 655 print("\t1. Check that the OWNERS file is in the correct place. It is " 656 "currently at:" 657 "\n\t\t" + owners_file_path) 658 print("\t2. Add your name (and others) to the OWNERS file:" + \ 659 "\n\t\t" + owners_file_path) 660 print("\t3. Check that the correct library version is assigned in the " 661 "build.gradle:" 662 "\n\t\t" + build_gradle_path) 663 print("\t4. Fill out the project/module name in the build.gradle:" 664 "\n\t\t" + build_gradle_path) 665 print("\t5. Update the project/module package documentation:" 666 "\n\t\t" + package_docs_path) 667 668def main(args): 669 # Parse arguments and check for existence of build ID or file 670 args = parser.parse_args() 671 if not args.group_id or not args.artifact_id: 672 parser.error("You must specify a group_id and an artifact_id") 673 sys.exit(1) 674 if not validate_name(args.group_id, args.artifact_id): 675 sys.exit(1) 676 if is_compose_project(args.group_id, args.artifact_id): 677 project_type = ProjectType.KOTLIN 678 else: 679 project_type = ask_project_type() 680 insert_new_group_id_into_library_versions_toml( 681 args.group_id 682 ) 683 create_directories( 684 args.group_id, 685 args.artifact_id, 686 project_type, 687 is_compose_project(args.group_id, args.artifact_id) 688 ) 689 update_settings_gradle(args.group_id, args.artifact_id) 690 update_docs_tip_of_tree_build_grade(args.group_id, args.artifact_id) 691 print("Created directories. \nRunning updateApi for the new " 692 "library, this may take a minute...", end='') 693 if run_update_api(args.group_id, args.artifact_id): 694 print("done.") 695 else: 696 print("failed. Please investigate manually.") 697 print_todo_list(args.group_id, args.artifact_id, project_type) 698 699if __name__ == '__main__': 700 main(sys.argv) 701