| OLD | NEW |
| 1 #!/usr/bin/python2.6 | 1 #!/usr/bin/python2.6 |
| 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Program to run emerge in parallel, for significant speedup. | 6 """Program to run emerge in parallel, for significant speedup. |
| 7 | 7 |
| 8 Usage: | 8 Usage: |
| 9 ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps] | 9 ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps] |
| 10 [--force-remote-binary=PKGS] [emerge args] package | 10 [--force-remote-binary=PKGS] [emerge args] package |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 75 # TODO(davidjames): Update Portage to expose public APIs for these features. | 75 # TODO(davidjames): Update Portage to expose public APIs for these features. |
| 76 from _emerge.actions import adjust_configs | 76 from _emerge.actions import adjust_configs |
| 77 from _emerge.actions import load_emerge_config | 77 from _emerge.actions import load_emerge_config |
| 78 from _emerge.create_depgraph_params import create_depgraph_params | 78 from _emerge.create_depgraph_params import create_depgraph_params |
| 79 from _emerge.depgraph import depgraph as emerge_depgraph | 79 from _emerge.depgraph import depgraph as emerge_depgraph |
| 80 from _emerge.depgraph import _frozen_depgraph_config | 80 from _emerge.depgraph import _frozen_depgraph_config |
| 81 from _emerge.main import emerge_main | 81 from _emerge.main import emerge_main |
| 82 from _emerge.main import parse_opts | 82 from _emerge.main import parse_opts |
| 83 from _emerge.Package import Package | 83 from _emerge.Package import Package |
| 84 from _emerge.Scheduler import Scheduler | 84 from _emerge.Scheduler import Scheduler |
| 85 from _emerge.SetArg import SetArg |
| 85 from _emerge.stdout_spinner import stdout_spinner | 86 from _emerge.stdout_spinner import stdout_spinner |
| 86 import portage | 87 import portage |
| 87 import portage.debug | 88 import portage.debug |
| 88 import portage.versions | 89 import portage.versions |
| 89 | 90 |
| 91 new_portage = not portage.VERSION.startswith("2.1.7.") |
| 92 if new_portage: |
| 93 from portage._global_updates import _global_updates |
| 94 else: |
| 95 from portage import _global_updates |
| 90 | 96 |
| 91 def Usage(): | 97 def Usage(): |
| 92 """Print usage.""" | 98 """Print usage.""" |
| 93 print "Usage:" | 99 print "Usage:" |
| 94 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps]" | 100 print " ./parallel_emerge [--board=BOARD] [--workon=PKGS] [--no-workon-deps]" |
| 95 print " [--rebuild] [emerge args] package" | 101 print " [--rebuild] [emerge args] package" |
| 96 print | 102 print |
| 97 print "Packages specified as workon packages are always built from source." | 103 print "Packages specified as workon packages are always built from source." |
| 98 print "Unless --no-workon-deps is specified, packages that depend on these" | 104 print "Unless --no-workon-deps is specified, packages that depend on these" |
| 99 print "packages are also built from source." | 105 print "packages are also built from source." |
| (...skipping 283 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 383 settings, trees, mtimedb = load_emerge_config() | 389 settings, trees, mtimedb = load_emerge_config() |
| 384 | 390 |
| 385 # Check whether our portage tree is out of date. Typically, this happens | 391 # Check whether our portage tree is out of date. Typically, this happens |
| 386 # when you're setting up a new portage tree, such as in setup_board and | 392 # when you're setting up a new portage tree, such as in setup_board and |
| 387 # make_chroot. In that case, portage applies a bunch of global updates | 393 # make_chroot. In that case, portage applies a bunch of global updates |
| 388 # here. Once the updates are finished, we need to commit any changes | 394 # here. Once the updates are finished, we need to commit any changes |
| 389 # that the global update made to our mtimedb, and reload the config. | 395 # that the global update made to our mtimedb, and reload the config. |
| 390 # | 396 # |
| 391 # Portage normally handles this logic in emerge_main, but again, we can't | 397 # Portage normally handles this logic in emerge_main, but again, we can't |
| 392 # use that function here. | 398 # use that function here. |
| 393 if portage._global_updates(trees, mtimedb["updates"]): | 399 if _global_updates(trees, mtimedb["updates"]): |
| 394 mtimedb.commit() | 400 mtimedb.commit() |
| 395 settings, trees, mtimedb = load_emerge_config(trees=trees) | 401 settings, trees, mtimedb = load_emerge_config(trees=trees) |
| 396 | 402 |
| 397 # Setup implied options. Portage normally handles this logic in | 403 # Setup implied options. Portage normally handles this logic in |
| 398 # emerge_main. | 404 # emerge_main. |
| 399 if "--buildpkgonly" in opts or "buildpkg" in settings.features: | 405 if "--buildpkgonly" in opts or "buildpkg" in settings.features: |
| 400 opts.setdefault("--buildpkg", True) | 406 opts.setdefault("--buildpkg", True) |
| 401 if "--getbinpkgonly" in opts: | 407 if "--getbinpkgonly" in opts: |
| 402 opts.setdefault("--usepkgonly", True) | 408 opts.setdefault("--usepkgonly", True) |
| 403 opts.setdefault("--getbinpkg", True) | 409 opts.setdefault("--getbinpkg", True) |
| (...skipping 27 matching lines...) Expand all Loading... |
| 431 adjust_configs(opts, trees) | 437 adjust_configs(opts, trees) |
| 432 | 438 |
| 433 # Save our configuration so far in the emerge object | 439 # Save our configuration so far in the emerge object |
| 434 emerge = self.emerge | 440 emerge = self.emerge |
| 435 emerge.action, emerge.opts = action, opts | 441 emerge.action, emerge.opts = action, opts |
| 436 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb | 442 emerge.settings, emerge.trees, emerge.mtimedb = settings, trees, mtimedb |
| 437 emerge.cmdline_packages = cmdline_packages | 443 emerge.cmdline_packages = cmdline_packages |
| 438 root = settings["ROOT"] | 444 root = settings["ROOT"] |
| 439 emerge.root_config = trees[root]["root_config"] | 445 emerge.root_config = trees[root]["root_config"] |
| 440 | 446 |
| 447 if new_portage and "--usepkg" in opts: |
| 448 emerge.trees[root]["bintree"].populate("--getbinpkg" in opts) |
| 449 |
| 441 def CheckUseFlags(self, pkgsettings, cur_pkg, new_pkg): | 450 def CheckUseFlags(self, pkgsettings, cur_pkg, new_pkg): |
| 442 """Are the use flags in cur_pkg up to date? | 451 """Are the use flags in cur_pkg up to date? |
| 443 | 452 |
| 444 Return True if use flags are up to date; return false otherwise.""" | 453 Return True if use flags are up to date; return false otherwise.""" |
| 445 | 454 |
| 446 # cur_use: The set of flags that were enabled when the package was | 455 # cur_use: The set of flags that were enabled when the package was |
| 447 # first installed. | 456 # first installed. |
| 448 # cur_iuse: The set of flags that affected the specified package | 457 # cur_iuse: The set of flags that affected the specified package |
| 449 # when it was first installed. | 458 # when it was first installed. |
| 450 # | 459 # |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 491 # specifying --tree as well, we tell emerge that it's not safe to remove | 500 # specifying --tree as well, we tell emerge that it's not safe to remove |
| 492 # uninstall instructions because we're planning on analyzing the output. | 501 # uninstall instructions because we're planning on analyzing the output. |
| 493 emerge_opts["--tree"] = True | 502 emerge_opts["--tree"] = True |
| 494 emerge_opts["--emptytree"] = True | 503 emerge_opts["--emptytree"] = True |
| 495 | 504 |
| 496 # Set up parameters. | 505 # Set up parameters. |
| 497 params = create_depgraph_params(emerge_opts, emerge.action) | 506 params = create_depgraph_params(emerge_opts, emerge.action) |
| 498 frozen_config = _frozen_depgraph_config(emerge.settings, emerge.trees, | 507 frozen_config = _frozen_depgraph_config(emerge.settings, emerge.trees, |
| 499 emerge_opts, emerge.spinner) | 508 emerge_opts, emerge.spinner) |
| 500 backtrack_max = emerge_opts.get('--backtrack', 5) | 509 backtrack_max = emerge_opts.get('--backtrack', 5) |
| 501 runtime_pkg_mask = None | 510 backtrack_parameters = {} |
| 502 allow_backtracking = backtrack_max > 0 | 511 allow_backtracking = backtrack_max > 0 |
| 503 | 512 |
| 504 # Try up to backtrack_max times to create a working depgraph. Each time we | 513 # Try up to backtrack_max times to create a working depgraph. Each time we |
| 505 # run into a conflict, mask the offending package and try again. | 514 # run into a conflict, mask the offending package and try again. |
| 506 # TODO(davidjames): When Portage supports --force-remote-binary directly, | 515 # TODO(davidjames): When Portage supports --force-remote-binary directly, |
| 507 # switch back to using the backtrack_depgraph function. | 516 # switch back to using the backtrack_depgraph function. |
| 508 for i in range(backtrack_max + 1): | 517 for i in range(backtrack_max + 2): |
| 509 if i == backtrack_max: | |
| 510 # Looks like we hit the backtracking limit. Run the dependency | |
| 511 # calculation one more time (from scratch) to show the original error | |
| 512 # message. | |
| 513 runtime_pkg_mask = None | |
| 514 allow_backtracking = False | |
| 515 | |
| 516 # Create a depgraph object. | 518 # Create a depgraph object. |
| 517 depgraph = emerge_depgraph(emerge.settings, emerge.trees, emerge_opts, | 519 depgraph = emerge_depgraph(emerge.settings, emerge.trees, emerge_opts, |
| 518 params, emerge.spinner, frozen_config=frozen_config, | 520 params, emerge.spinner, frozen_config=frozen_config, |
| 519 allow_backtracking=allow_backtracking, | 521 allow_backtracking=allow_backtracking, |
| 520 runtime_pkg_mask=runtime_pkg_mask) | 522 **backtrack_parameters) |
| 521 | 523 |
| 522 if i == 0: | 524 if i == 0: |
| 523 for cpv in self.forced_remote_binary_packages: | 525 for cpv in self.forced_remote_binary_packages: |
| 524 # If --force-remote-binary was specified, we want to use this package | 526 # If --force-remote-binary was specified, we want to use this package |
| 525 # regardless of its use flags. Unfortunately, Portage doesn't support | 527 # regardless of its use flags. Unfortunately, Portage doesn't support |
| 526 # ignoring use flags for just one package. To convince Portage to | 528 # ignoring use flags for just one package. To convince Portage to |
| 527 # install the package, we trick Portage into thinking the package has | 529 # install the package, we trick Portage into thinking the package has |
| 528 # the right use flags. | 530 # the right use flags. |
| 529 # TODO(davidjames): Update Portage to support --force-remote-binary | 531 # TODO(davidjames): Update Portage to support --force-remote-binary |
| 530 # directly, so that this hack isn't necessary. | 532 # directly, so that this hack isn't necessary. |
| 531 pkg = depgraph._pkg(cpv, "binary", emerge.root_config) | 533 pkg = depgraph._pkg(cpv, "binary", emerge.root_config) |
| 532 pkgsettings = frozen_config.pkgsettings[pkg.root] | 534 pkgsettings = frozen_config.pkgsettings[pkg.root] |
| 533 pkgsettings.setcpv(pkg) | 535 pkgsettings.setcpv(pkg) |
| 534 pkg.use.enabled = pkgsettings["PORTAGE_USE"].split() | 536 pkg.use.enabled = pkgsettings["PORTAGE_USE"].split() |
| 535 | 537 |
| 536 # Select the packages we want. | 538 # Select the packages we want. |
| 537 success, favorites = depgraph.select_files(packages) | 539 success, favorites = depgraph.select_files(packages) |
| 538 if success: | 540 if success: |
| 539 break | 541 break |
| 540 elif depgraph.need_restart(): | 542 elif depgraph.need_restart() and i < backtrack_max: |
| 541 # Looks like we found some packages that can't be installed due to | 543 # Looks like we found some packages that can't be installed due to |
| 542 # conflicts. Try again, masking out the conflicting packages. | 544 # conflicts. Try again, masking out the conflicting packages. |
| 543 runtime_pkg_mask = depgraph.get_runtime_pkg_mask() | 545 if new_portage: |
| 546 backtrack_parameters = depgraph.get_backtrack_parameters() |
| 547 else: |
| 548 backtrack_parameters = { |
| 549 'runtime_pkg_mask': depgraph.get_runtime_pkg_mask() |
| 550 } |
| 544 elif allow_backtracking and i > 0: | 551 elif allow_backtracking and i > 0: |
| 545 # Looks like we tried all the possible combinations, and we still can't | 552 # Looks like we can't solve the graph. Stop backtracking and report an |
| 546 # solve the graph. Stop backtracking, so that we can report an error | 553 # error message. |
| 547 # message. | 554 backtrack_parameters.pop('runtime_pkg_mask', None) |
| 548 runtime_pkg_mask = None | |
| 549 allow_backtracking = False | 555 allow_backtracking = False |
| 550 else: | 556 else: |
| 551 break | 557 break |
| 552 | 558 |
| 553 # Delete the --tree option, because we don't really want to display a | 559 # Delete the --tree option, because we don't really want to display a |
| 554 # tree. We just wanted to get emerge to leave uninstall instructions on | 560 # tree. We just wanted to get emerge to leave uninstall instructions on |
| 555 # the graph. Later, when we display the graph, we'll want standard-looking | 561 # the graph. Later, when we display the graph, we'll want standard-looking |
| 556 # output, so removing the --tree option is important. | 562 # output, so removing the --tree option is important. |
| 557 frozen_config.myopts.pop("--tree", None) | 563 frozen_config.myopts.pop("--tree", None) |
| 558 | 564 |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 634 # | 640 # |
| 635 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1 | 641 # Here's an example CPV: chromeos-base/power_manager-0.0.1-r1 |
| 636 # Split up, this CPV would be: | 642 # Split up, this CPV would be: |
| 637 # C -- Component: chromeos-base | 643 # C -- Component: chromeos-base |
| 638 # P -- Path: power_manager | 644 # P -- Path: power_manager |
| 639 # V -- Version: 0.0.1-r1 | 645 # V -- Version: 0.0.1-r1 |
| 640 # | 646 # |
| 641 # We just refer to CPVs as packages here because it's easier. | 647 # We just refer to CPVs as packages here because it's easier. |
| 642 deps = {} | 648 deps = {} |
| 643 for child, priorities in node_deps[0].items(): | 649 for child, priorities in node_deps[0].items(): |
| 650 if isinstance(child, SetArg): continue |
| 644 deps[str(child.cpv)] = dict(action=str(child.operation), | 651 deps[str(child.cpv)] = dict(action=str(child.operation), |
| 645 deptype=str(priorities[-1]), | 652 deptype=str(priorities[-1]), |
| 646 deps={}) | 653 deps={}) |
| 647 | 654 |
| 648 # We've built our list of deps, so we can add our package to the tree. | 655 # We've built our list of deps, so we can add our package to the tree. |
| 649 if isinstance(node, Package): | 656 if isinstance(node, Package): |
| 650 deps_tree[str(node.cpv)] = dict(action=str(node.operation), | 657 deps_tree[str(node.cpv)] = dict(action=str(node.operation), |
| 651 deps=deps) | 658 deps=deps) |
| 652 | 659 |
| 653 emptytree = "--emptytree" in emerge.opts | 660 emptytree = "--emptytree" in emerge.opts |
| (...skipping 531 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1185 remote_ready = PrebuiltsReady(pkg, remote_pkgs, remote_ready_cache) | 1192 remote_ready = PrebuiltsReady(pkg, remote_pkgs, remote_ready_cache) |
| 1186 if remote_ready and (local_mtime <= remote_mtime or pkg in cycles): | 1193 if remote_ready and (local_mtime <= remote_mtime or pkg in cycles): |
| 1187 MergeChildren(pkg, "mandatory") | 1194 MergeChildren(pkg, "mandatory") |
| 1188 else: | 1195 else: |
| 1189 MergeChildren(pkg, "mandatory_source") | 1196 MergeChildren(pkg, "mandatory_source") |
| 1190 | 1197 |
| 1191 def UsePrebuiltPackages(remote_pkgs): | 1198 def UsePrebuiltPackages(remote_pkgs): |
| 1192 """Update packages that can use prebuilts to do so.""" | 1199 """Update packages that can use prebuilts to do so.""" |
| 1193 start = time.time() | 1200 start = time.time() |
| 1194 | 1201 |
| 1195 # Build list of prebuilt packages | 1202 # Build list of prebuilt packages. |
| 1196 prebuilt_pkgs = {} | 1203 prebuilt_pkgs = {} |
| 1197 for pkg, info in deps_map.iteritems(): | 1204 for pkg, info in deps_map.iteritems(): |
| 1198 if info and info["action"] == "merge": | 1205 if info and info["action"] == "merge": |
| 1199 if (not info["force_remote_binary"] and info["mandatory_source"] or | 1206 if (not info["force_remote_binary"] and info["mandatory_source"] or |
| 1200 "--usepkgonly" not in emerge.opts and pkg not in remote_pkgs): | 1207 "--usepkgonly" not in emerge.opts and pkg not in remote_pkgs): |
| 1201 continue | 1208 continue |
| 1202 | 1209 |
| 1203 db_pkg = emerge.depgraph._pkg(pkg, "binary", emerge.root_config) | 1210 db_pkg = emerge.depgraph._pkg(pkg, "binary", emerge.root_config) |
| 1204 if info["force_remote_binary"]: | 1211 if info["force_remote_binary"]: |
| 1205 # Undo our earlier hacks to the use flags so that the use flags | 1212 # Undo our earlier hacks to the use flags so that the use flags |
| (...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1387 It expects package identifiers to be passed to it via task_queue. When | 1394 It expects package identifiers to be passed to it via task_queue. When |
| 1388 a task is started, it pushes the (target, filename) to the started_queue. | 1395 a task is started, it pushes the (target, filename) to the started_queue. |
| 1389 The output is stored in filename. When a merge starts or finishes, we push | 1396 The output is stored in filename. When a merge starts or finishes, we push |
| 1390 EmergeJobState objects to the job_queue. | 1397 EmergeJobState objects to the job_queue. |
| 1391 """ | 1398 """ |
| 1392 | 1399 |
| 1393 SetupWorkerSignals() | 1400 SetupWorkerSignals() |
| 1394 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb | 1401 settings, trees, mtimedb = emerge.settings, emerge.trees, emerge.mtimedb |
| 1395 opts, spinner = emerge.opts, emerge.spinner | 1402 opts, spinner = emerge.opts, emerge.spinner |
| 1396 opts["--nodeps"] = True | 1403 opts["--nodeps"] = True |
| 1404 if new_portage: |
| 1405 # When Portage launches new processes, it goes on a rampage and closes all |
| 1406 # open file descriptors. Ask Portage not to do that, as it breaks us. |
| 1407 portage.process.get_open_fds = lambda: [] |
| 1397 while True: | 1408 while True: |
| 1398 # Wait for a new item to show up on the queue. This is a blocking wait, | 1409 # Wait for a new item to show up on the queue. This is a blocking wait, |
| 1399 # so if there's nothing to do, we just sit here. | 1410 # so if there's nothing to do, we just sit here. |
| 1400 target = task_queue.get() | 1411 target = task_queue.get() |
| 1401 if not target: | 1412 if not target: |
| 1402 # If target is None, this means that the main thread wants us to quit. | 1413 # If target is None, this means that the main thread wants us to quit. |
| 1403 # The other workers need to exit too, so we'll push the message back on | 1414 # The other workers need to exit too, so we'll push the message back on |
| 1404 # to the queue so they'll get it too. | 1415 # to the queue so they'll get it too. |
| 1405 task_queue.put(target) | 1416 task_queue.put(target) |
| 1406 return | 1417 return |
| 1407 db_pkg = package_db[target] | 1418 db_pkg = package_db[target] |
| 1408 db_pkg.root_config = emerge.root_config | 1419 db_pkg.root_config = emerge.root_config |
| 1409 install_list = [db_pkg] | 1420 install_list = [db_pkg] |
| 1410 pkgname = db_pkg.pf | 1421 pkgname = db_pkg.pf |
| 1411 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False) | 1422 output = tempfile.NamedTemporaryFile(prefix=pkgname + "-", delete=False) |
| 1412 start_timestamp = time.time() | 1423 start_timestamp = time.time() |
| 1413 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp) | 1424 job = EmergeJobState(target, pkgname, False, output.name, start_timestamp) |
| 1414 job_queue.put(job) | 1425 job_queue.put(job) |
| 1415 if "--pretend" in opts: | 1426 if "--pretend" in opts: |
| 1416 retcode = 0 | 1427 retcode = 0 |
| 1417 else: | 1428 else: |
| 1418 save_stdout = sys.stdout | 1429 save_stdout = sys.stdout |
| 1419 save_stderr = sys.stderr | 1430 save_stderr = sys.stderr |
| 1420 try: | 1431 try: |
| 1421 sys.stdout = output | 1432 sys.stdout = output |
| 1422 sys.stderr = output | 1433 sys.stderr = output |
| 1423 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner, | 1434 if new_portage: |
| 1424 install_list, [], emerge.scheduler_graph) | 1435 emerge.scheduler_graph.mergelist = install_list |
| 1436 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner, |
| 1437 favorites=[], graph_config=emerge.scheduler_graph) |
| 1438 else: |
| 1439 scheduler = Scheduler(settings, trees, mtimedb, opts, spinner, |
| 1440 install_list, [], emerge.scheduler_graph) |
| 1425 retcode = scheduler.merge() | 1441 retcode = scheduler.merge() |
| 1426 except Exception: | 1442 except Exception: |
| 1427 traceback.print_exc(file=output) | 1443 traceback.print_exc(file=output) |
| 1428 retcode = 1 | 1444 retcode = 1 |
| 1429 finally: | 1445 finally: |
| 1430 sys.stdout = save_stdout | 1446 sys.stdout = save_stdout |
| 1431 sys.stderr = save_stderr | 1447 sys.stderr = save_stderr |
| 1432 output.close() | 1448 output.close() |
| 1433 if retcode is None: | 1449 if retcode is None: |
| 1434 retcode = 0 | 1450 retcode = 0 |
| (...skipping 441 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1876 # need to upgrade the rest of the packages. So we'll go ahead and do that. | 1892 # need to upgrade the rest of the packages. So we'll go ahead and do that. |
| 1877 if portage_upgrade: | 1893 if portage_upgrade: |
| 1878 args = sys.argv[1:] + ["--nomerge=sys-apps/portage"] | 1894 args = sys.argv[1:] + ["--nomerge=sys-apps/portage"] |
| 1879 os.execvp(os.path.realpath(sys.argv[0]), args) | 1895 os.execvp(os.path.realpath(sys.argv[0]), args) |
| 1880 | 1896 |
| 1881 print "Done" | 1897 print "Done" |
| 1882 sys.exit(0) | 1898 sys.exit(0) |
| 1883 | 1899 |
| 1884 if __name__ == "__main__": | 1900 if __name__ == "__main__": |
| 1885 main() | 1901 main() |
| OLD | NEW |