Mark Cogan | 0abda965 | 2020-04-15 11:22:07 | [diff] [blame] | 1 | # Working With Files |
| 2 | |
| 3 | Adding, removing, and renaming files in iOS Chromium needs to follow a specific |
| 4 | procedure that will be unfamiliar to engineers coming from other iOS projects. |
| 5 | Conceptually, every file is recorded in _four_ locations: the local filesystem, |
| 6 | git, the `BUILD.gn` files, and the XCode projects. Of these, the XCode project |
| 7 | is wholly generated from the others. |
| 8 | |
| 9 | [TOC] |
| 10 | |
| 11 | ## Overview |
| 12 | |
| 13 | **Do not use XCode to manipulate files.** The XCode project used for iOS |
| 14 | Chromium is _generated_; it's functionally a build artifact. The various |
| 15 | `BUILD.gn` files in Chromium define structure of the XCode project file. Running |
| 16 | `gclient runhooks` causes the project files to be regenerated. |
| 17 | |
| 18 | Individual files can have their contents edited within XCode, and all of the |
| 19 | regular testing and debugging activities can be done within XCode. It just can't |
| 20 | be used to create files, rename files, delete files, or manipulate the project |
| 21 | group structure in any way. To do these things, follow the procedures below. |
| 22 | |
| 23 | ## Adding files |
| 24 | |
| 25 | To add any files (new headers, `.mm` or `.cc` implementation files, asset files |
| 26 | of any kind), the following general steps need to happen: |
| 27 | |
| 28 | 1. The file needs to exist in the correct directory of the file system for your |
| 29 | Chromium checkout. New files need to be created, or assets need to be |
| 30 | copied to the right location. |
| 31 | |
| 32 | 1. The new files need to be added to git. |
| 33 | |
| 34 | 1. The new files need to be added to a target in a `BUILD.gn` file (usually in |
| 35 | the same directory as the newly added file). |
| 36 | |
| 37 | 1. The XCode project needs to be regenerated. |
| 38 | |
| 39 | For adding new header or implementation files, the following procedure is |
| 40 | recommended: |
| 41 | |
| 42 | 1. Generate the new files using `tools/boilerplate.py`. This will generate the |
| 43 | correct header guard macros and include the copyright boilerplate. |
| 44 | |
| 45 | 2. Add the newly created files using `git add`. |
| 46 | |
| 47 | 3. Edit the `BUILD.gn` file for the directory where the files were added. For |
| 48 | each file, add it to the `sources` list for the correct `source_set` in the |
| 49 | `BUILD.gn` file. Note that `gn format` (which is run as part of `git cl |
| 50 | format`) will take care of alphabetizing the lists along with other |
| 51 | formatting, so there's no need to manually do these things. (`BUILD.gn` |
| 52 | files cand be edited directly in XCode, or in another editor such as `vi`). |
| 53 | |
| 54 | 4. Once all of the files have been created, `add`ed and all `BUILD.gn` files |
| 55 | have been updated, run `gclient runhooks` to regenerate all XCode projects. |
| 56 | |
| 57 | 5. If XCode is open, it may prompt to "Autocreate Schemes". If so, click on the |
| 58 | highlighted "Automatically Create Schemes" button. |
| 59 | |
| 60 | In the shell, this procedure would look like this: |
| 61 | ```bash |
| 62 | // Step 1 -- generate new files. |
| 63 | $ tools/boilerplate.py ios/chrome/browser/some_feature/feature_class.h |
| 64 | $ tools/boilerplate.py ios/chrome/browser/some_feature/feature_class.mm |
| 65 | $ tools/boilerplate.py ios/chrome/browser/some_feature/feature_class_unittest.mm |
| 66 | // Step 2 -- add the new files. |
| 67 | $ git add ios/chrome/browser/some_feature/feature_class* |
| 68 | // Step 3 -- edit the BUILD.gn file in the editor of your choice |
| 69 | $ vi ios/chrome/browser/some_feature/BUILD.gn |
| 70 | // Step 4 -- regenerate the XCode Projects |
| 71 | $ gclient runhooks |
| 72 | ``` |
| 73 | |
| 74 | To add asset files, follow this procedure: |
| 75 | |
| 76 | 1. Copy the asset files to the correct directory, with the correct names |
| 77 | (including `@2x` and `@3x` suffixes) Note that images are stored in |
| 78 | `.imageset` directories, conventionally inside `resources` directories for a |
| 79 | given UI feature. New directories, if needed, are created in the usual way |
| 80 | (`mkdir`). Note that there is no equivalent of `boilerplate.py` for images |
| 81 | or other asset files. |
| 82 | |
| 83 | 1. Create or copy `Contents.json` files for each new `.imageset` directory, |
| 84 | with the appropriate contents. |
| 85 | |
| 86 | 1. Add all image and `Contents.json` files using `git add`. |
| 87 | |
| 88 | 1. Edit the `BUILD.gn` file in the containing `resources` directory, adding |
| 89 | `imageset` entries for each added `.imageset` directory, and then grouping |
| 90 | all assets into a new or existing `group()` declaration with a `public_deps` |
| 91 | list containing all of the `imageset` targets. |
| 92 | |
| 93 | 1. Regenerate the XCode project with `gclient runhooks`. |
| 94 | |
| 95 | To add Markdown documentation files, the procedure is much simpler. These files |
| 96 | are automatically added to the XCode project without `BUILD.gn` entries, and |
| 97 | they have no required boilerplate. So adding new docs is as simple as: |
| 98 | |
| 99 | 1. Create a new `.md` file in the appropriate directory (`docs/ios`, for |
| 100 | example). |
| 101 | |
| 102 | 1. `git add` the file. |
| 103 | |
| 104 | The newly added file will be visible in XCode after the next `gclient runhooks`. |
| 105 | |
| 106 | ## Moving and renaming files. |
| 107 | |
| 108 | Renaming a file involves updating the filename in all of the places where it |
| 109 | exists: the file names in the filesystem, the file names in git, header guards |
| 110 | in files, import declarations in files, listings in BUILD.gn files, and |
| 111 | internally in the XCode project. As with adding a file, different tools are used |
| 112 | for each of these. Unlike creating a file, which starts with actually adding a |
| 113 | file to the filesystem, a rename starts with updating git (via `git mv`), then |
| 114 | using the `mass_rename` tool to update file contents. |
| 115 | |
| 116 | `tools/git/mass_rename.py` works by looking at _uncommitted_ file moves in git, |
| 117 | and then updating all includes, header guards, and BUILD.gn entries to use the |
| 118 | new name. It doesn't update some other files, such as `Contents.json` files for |
| 119 | image assets. It also doesn't change any symbols in code, so class and variable |
| 120 | names won't be changed. |
| 121 | |
| 122 | For many file moves, it will be simpler to use another tool, |
| 123 | `tools/git/move_source_file.py`, which combines `git mv` and `mass_rename` in a |
| 124 | single action. For example, renaming `feature_class` to `renamed_class` would be |
| 125 | done like this: |
| 126 | ```bash |
| 127 | $ tools/git/move_source_file.py ios/chrome/browser/some_feature/feature_class.h \ |
| 128 | ios/chrome/browser/some_feature/renamed_class.h |
| 129 | $ tools/git/move_source_file.py ios/chrome/browser/some_feature/feature_class.mm \ |
| 130 | ios/chrome/browser/some_feature/renamed_class.mm |
| 131 | ``` |
| 132 | |
| 133 | The step-by-step procedure for a rename is: |
| 134 | |
| 135 | 1. If there are other uncommitted changes before the move, it's usually |
| 136 | cleanest to commit before starting the move. |
| 137 | |
| 138 | 1. `move_source_file` each file that needs to be renamed. This renames the file |
| 139 | in both the file system and in git, and in most places where it's used in |
| 140 | code. |
| 141 | |
| 142 | 1. Run `gclient runhooks` to update the XCode project. Check that all of the |
| 143 | needed name changes have been made (for example, by building all targets). |
| 144 | Make any other needed fixes. |
| 145 | |
| 146 | 1. If any classes or other symbols need to be renamed (remember that the name |
| 147 | of the primary interface in each file must match the file name), make those |
| 148 | changes. Find-and-replace tools like `tools/git/mffr.py` or XCode's |
| 149 | Find/Replace can help here, but there are no compiler-aware tools that can |
| 150 | do a "smart" rename. |
| 151 | |
| 152 | 1. Commit all changes (`git commit -a -m <your comment>`). |
| 153 | |
| 154 | A move—where a file is moved to a different directory—is in most respects |
| 155 | performed using the same steps as a rename. However, while `mass_rename.py` (and |
| 156 | thus `move_source_file.py`) will update existing file names in `BUILD.gn` files, |
| 157 | it won't move entries from one `BUILD.gn` file to another. To move files to a |
| 158 | different directory, the preceding procedure is used, but between steps 2 and 3 |
| 159 | (after moving the files, but before regenerating the XCode project), the old |
| 160 | filenames will need to be removed from the `BUILD.gn` files in the old |
| 161 | directories and added to the `BUILD.gn` files in the new directories. |
| 162 | |
| 163 | Also note that while `move_source_file` must be used separately for each file |
| 164 | being renamed within a directory, it (just like `git mv`) can move multiple |
| 165 | files without renaming to a new directory in a single command: |
| 166 | |
| 167 | ```bash |
| 168 | $ tools/git/mass_rename.py ios/chrome/browser/some_feature/feature_class.* \ |
| 169 | ios/chrome/browser/some_feature/feature_class_unittest.mm \ |
| 170 | ios/chrome/browser/other_feature/ |
| 171 | ``` |
| 172 | |
| 173 | ## Deleting files. |
| 174 | |
| 175 | Deleting files follows the same patterns as adding and moving files. As with a |
| 176 | file move, it's best to begin with deleting the files from git. |
| 177 | |
| 178 | Typically, before actually removing a file, first all usage of the interface(s) |
| 179 | in the file(s) will be removed, and the file will no longer be `#imported` |
| 180 | anywhere. |
| 181 | |
| 182 | Step-by step: |
| 183 | |
| 184 | 1. `git rm` the files you want to remove. This will also remove the files |
| 185 | from the filesystem. |
| 186 | |
| 187 | 1. Manually remove the `BUILD.gn` entries for the files. |
| 188 | |
| 189 | 1. Regenerate the XCode project (with `gclient runhooks`) to remove the files |
| 190 | from XCode. |
| 191 | |
| 192 | ## Finally. |
| 193 | |
| 194 | It's easy to miss some uses of a file that was renamed or deleted, and fixing |
| 195 | compilation errors discovered in the commit queue means another |
| 196 | commit-upload-dry run cycle (at least). To minimize this, after any change that |
| 197 | adds, renames, moves, or deletes files, be sure to take the following steps: |
| 198 | |
| 199 | 1. `git cl format` to update the formatting of all files. |
| 200 | |
| 201 | 1. `gn check` to make sure that any new or moved files follow the dependency |
| 202 | rules (for example: `gn check -C out/Debug-iphonesimulator/`). |
| 203 | |
| 204 | 1. Build all targets, to make sure that everything has been added, changed, or |
| 205 | removed correctly. This can be done by selecting the "All" target in XCode |
| 206 | and building (`⌘-B`), or from the command line (for example, `autoninja -C |
| 207 | out/Debug-iphonesimulator/`). |
| 208 | |
| 209 | Changes that involve adding or deleting more than a few files, and most renames |
| 210 | of any size, should be in a single CL with no other changes, for ease of |
| 211 | reviewing and (if necessary) reverting or cherry-picking. |
| 212 | |
| 213 | ## Recovering from accidental XCode Project usage. |
| 214 | |
| 215 | If files are accidentally added, renamed, or moved through XCode, other settings |
| 216 | in the XCode project may be changed that will introduce strange local build |
| 217 | failures. In this case, take the following steps to recover. |
| 218 | |
| 219 | 1. Quit XCode. |
| 220 | |
| 221 | 1. Delete all generated XCode projects and associated files: `rm -rf out/build`. |
| 222 | |
| 223 | 1. Regenerate all XCode projects: `gclient runhooks`. |