| 454 | self.project_file.EnsureNoIDCollisions() |
| 455 | |
| 456 | def Write(self): |
| 457 | # Write the project file to a temporary location first. Xcode watches for |
| 458 | # changes to the project file and presents a UI sheet offering to reload |
| 459 | # the project when it does change. However, in some cases, especially when |
| 460 | # multiple projects are open or when Xcode is busy, things don't work so |
| 461 | # seamlessly. Sometimes, Xcode is able to detect that a project file has |
| 462 | # changed but can't unload it because something else is referencing it. |
| 463 | # To mitigate this problem, and to avoid even having Xcode present the UI |
| 464 | # sheet when an open project is rewritten for inconsequential changes, the |
| 465 | # project file is written to a temporary file in the xcodeproj directory |
| 466 | # first. The new temporary file is then compared to the existing project |
| 467 | # file, if any. If they differ, the new file replaces the old; otherwise, |
| 468 | # the new project file is simply deleted. Xcode properly detects a file |
| 469 | # being renamed over an open project file as a change and so it remains |
| 470 | # able to present the "project file changed" sheet under this system. |
| 471 | # Writing to a temporary file first also avoids the possible problem of |
| 472 | # Xcode rereading an incomplete project file. |
| 473 | (output_fd, new_pbxproj_path) = tempfile.mkstemp( |
| 474 | suffix=".tmp", prefix="project.pbxproj.gyp.", dir=self.path |
| 475 | ) |
| 476 | |
| 477 | try: |
| 478 | output_file = os.fdopen(output_fd, "w") |
| 479 | |
| 480 | self.project_file.Print(output_file) |
| 481 | output_file.close() |
| 482 | |
| 483 | pbxproj_path = os.path.join(self.path, "project.pbxproj") |
| 484 | |
| 485 | same = False |
| 486 | try: |
| 487 | same = filecmp.cmp(pbxproj_path, new_pbxproj_path, False) |
| 488 | except OSError as e: |
| 489 | if e.errno != errno.ENOENT: |
| 490 | raise |
| 491 | |
| 492 | if same: |
| 493 | # The new file is identical to the old one, just get rid of the new |
| 494 | # one. |
| 495 | os.unlink(new_pbxproj_path) |
| 496 | else: |
| 497 | # The new file is different from the old one, or there is no old one. |
| 498 | # Rename the new file to the permanent name. |
| 499 | # |
| 500 | # tempfile.mkstemp uses an overly restrictive mode, resulting in a |
| 501 | # file that can only be read by the owner, regardless of the umask. |
| 502 | # There's no reason to not respect the umask here, which means that |
| 503 | # an extra hoop is required to fetch it and reset the new file's mode. |
| 504 | # |
| 505 | # No way to get the umask without setting a new one? Set a safe one |
| 506 | # and then set it back to the old value. |
| 507 | umask = os.umask(0o77) |
| 508 | os.umask(umask) |
| 509 | |
| 510 | os.chmod(new_pbxproj_path, 0o666 & ~umask) |
| 511 | os.rename(new_pbxproj_path, pbxproj_path) |
| 512 | |
| 513 | except Exception: |