|
1 | | -# Introduction |
| 1 | +# Introduction to Packages |
2 | 2 |
|
3 | | -Packages are an extension to modules for HHVM and Hack which allows developers to more easily configure which files to build and deploy. |
| 3 | +Packages are a file-based mechanism for organizing code into logical units with explicit dependencies and fine-grained deployment control. |
4 | 4 |
|
5 | | -## Package definitions |
6 | | -A **package** is defined by a set of modules meant to be deployed together. Packages are configured in a toml file called `PACKAGES.toml` located at the root of your codebase (next to .hhconfig). |
| 5 | +## Key Concepts |
| 6 | + |
| 7 | +**Packages** group related files in your codebase (e.g., production code, test code, experimental features). |
| 8 | + |
| 9 | +**Deployments** define which packages to build and run together. |
| 10 | + |
| 11 | +**Package relationships:** Packages explicitly include other packages to access their symbols, creating a dependency graph. |
| 12 | + |
| 13 | +## Quick Example |
| 14 | + |
| 15 | +Packages are configured in a TOML file called `PACKAGES.toml` located at the root of your codebase (next to `.hhconfig`): |
7 | 16 |
|
8 | 17 | ```toml PACKAGES.toml |
9 | 18 | [packages] |
10 | 19 |
|
11 | 20 | [packages.production] |
12 | | -uses=["prod.*", "my_prod"] # Package contains all modules that start with `prod`, and the module "my_prod". |
| 21 | +include_paths = ["//flib/"] |
13 | 22 |
|
14 | 23 | [packages.test] |
15 | | -uses=["test.*"] |
16 | | -includes=["production"] # Package depends on the production package |
| 24 | +include_paths = ["//flib/test/"] |
| 25 | +includes = ["production"] # test depends on production |
| 26 | + |
| 27 | +[deployments] |
| 28 | + |
| 29 | +[deployments.production] |
| 30 | +packages = ["production"] |
| 31 | + |
| 32 | +[deployments.test] |
| 33 | +packages = ["test", "production"] # must include dependencies |
| 34 | +``` |
| 35 | + |
| 36 | +In this example: |
| 37 | +- The `production` package contains all files in `//flib/` (but not those in //flib/test/) |
| 38 | +- The `test` package contains files in `//flib/test/` and can access symbols defined in files in the production package |
| 39 | +- Two deployments are defined: one for production-only and one that includes tests |
| 40 | + |
| 41 | +## How Packages Work |
| 42 | + |
| 43 | +Each file in your codebase belongs to exactly one package. The same file path cannot appear in multiple packages' `include_paths` clauses. |
| 44 | + |
| 45 | +## File package membership |
| 46 | + |
| 47 | +Files can be assigned to packages in two ways: |
| 48 | + |
| 49 | +### 1. Via `include_paths` in PACKAGES.toml |
| 50 | + |
| 51 | +The `include_paths` clause specifies which files belong to a package: |
| 52 | +- Items are relative paths starting with `//` (representing the root directory) |
| 53 | +- A path ending with `/` (e.g., `"//flib/"`) includes all files recursively in that directory |
| 54 | +- A path without trailing `/` (e.g., `"//flib/foo.php"`) includes only that specific file |
| 55 | + |
| 56 | +### 2. Via `__PackageOverride` file attribute |
| 57 | + |
| 58 | +A file can override its package membership using the `__PackageOverride` attribute: |
| 59 | + |
| 60 | +```hack no-extract |
| 61 | +<?hh |
| 62 | +<<file: __PackageOverride('production')>> |
| 63 | +// This file now belongs to the 'production' package |
17 | 64 | ``` |
18 | 65 |
|
19 | | -Every module can be in at most one package, so the same module cannot be part of two packages. Therefore, the same glob cannot appear in the uses clause of two different modules. |
| 66 | +## Precedence rules |
| 67 | + |
| 68 | +When determining which package a file belongs to: |
| 69 | +1. If a file has a `__PackageOverride` attribute, it belongs to that package |
| 70 | +2. Otherwise, if the full (relative-to-php-root) file path appears in an `include_paths` clause, it belongs to that package |
| 71 | +3. Otherwise, the file belongs to the package whose `include_paths` contains the file's nearest ancestor directory (i.e. the longest prefix match) |
| 72 | +4. If none of the above apply, the file belongs to the **default package** |
| 73 | + |
| 74 | +## Default package |
| 75 | + |
| 76 | +Any file not specified in any package (neither via `include_paths` nor `__PackageOverride`) belongs to the implicit "default" package. Symbols within the default package are not accessible from other packages. The default package: |
| 77 | +- Cannot be explicitly defined with the name "default" (the name is reserved) |
| 78 | +- Cannot be referenced via its name in `includes` and `soft_includes` lists |
| 79 | +- Can be made explicit by creating a package that includes `"//"` |
| 80 | + |
| 81 | +**Example of making default explicit:** |
| 82 | +```toml |
| 83 | +[packages.orphans] |
| 84 | +include_paths = ["//"] |
| 85 | +``` |
20 | 86 |
|
21 | | -## Overlaps in module globs |
22 | | -- It is an error for the same module glob to appear in multiple package definitions. Given a module with name foo.bar, we resolve what package the module belongs to with the following precedence: |
23 | | -- If a package lists foo.bar directly in its use clause, foo.bar belongs to that package. If multiple packages list foo.bar directly, we throw an error at package definition. |
24 | | -- If no package directly references the module, but a package has a prefix which matches foo.bar (in this case, foo.* for example), foo.bar belongs to the package with that prefix. If multiple packages list a prefix of the module, we take the most specific prefix, i.e. the longest prefix that matches. |
25 | | -- If no package references the module in its use clause, the module belongs to the default package. |
| 87 | +This creates a package that captures all files not in other packages. |
26 | 88 |
|
27 | 89 | Once you've defined a set of packages, you can deploy them using [deployments](/docs/hack/packages/deployments). |
0 commit comments