Skip to content
This repository has been archived by the owner on Sep 1, 2023. It is now read-only.

Commit

Permalink
Pass through to facts (#72)
Browse files Browse the repository at this point in the history
* Documentation update
Remove implied comparisons to composer autoloading.
Remove statement about global namespace fallback.
Document interactions with native autoloaders.
Document HH\Facts mode (not present in this commit).

* [Untested] Emit an HH\Facts autoload map
May still need to lowercase the names.
Will check in a Facts-enabled docker container.

* Lowercase the names and call the right functions

* Parens

* Escape `HH\Facts`, no need for a random form feed

* Rename useFactsIfAvailableAndDoNotEmitAStaticMap
Now named useFactsIfAvailable

* Support hhvm 4.109 and up
Hhvm 4.116 and below need a HH_FIXME.
Hhvm 4.117 has the hhi and the code typechecks on 4.117.

* Update HH_FIXME messages
The ext-facts hhi didn't make the cut for 4.117.
  • Loading branch information
lexidor authored Jul 13, 2021
1 parent 73f576f commit 0b5be45
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
matrix:
os: [ ubuntu , macos ]
hhvm:
- '4.67'
- '4.109'
- latest
- nightly
runs-on: ${{matrix.os}}-latest
Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ The autoloader for autoloading classes, enums, functions, typedefs, and constant
Usage
=====

1. Add an `hh_autoload.json` file (see section below) and optionally remove your configuration from composer.json
1. Add an `hh_autoload.json` file (see section below)
2. `composer require hhvm/hhvm-autoload`
3. Replace any references to `vendor/autoload.php` with `vendor/autoload.hack` and call `Facebook\AutoloadMap\initialize()`
4. To re-generate the map, run `vendor/bin/hh-autoload`, `composer dump-autoload`, or any other command that generates the map
3. Require the autoload file from your entrypoint functions using `require_once (__DIR__ . '(/..)(repeat as needed)/vendor/autoload.hack');`
4. Call `Facebook\AutolaodMap\initialize()` to register the autoloader with hhvm.
5. To re-generate the map, run `vendor/bin/hh-autoload`, `composer dump-autoload`, or any other command that generates the map

Configuration (`hh_autoload.json`)
==================================
Expand All @@ -32,9 +33,10 @@ The following settings are optional:
- `"includeVendor": false` - do not include `vendor/` definitions in `vendor/autoload.hack`
- `"devRoots": [ "path/", ...]` - additional roots to only include in dev mode, not when installed as a dependency.
- `"relativeAutoloadRoot": false` - do not use a path relative to `__DIR__` for autoloading. Instead, use the path to the folder containing `hh_autoload.json` when building the autoload map.
- `"failureHandler:" classname<Facebook\AutoloadMap\FailureHandler>` - use the specified class to handle definitions that aren't the Map. Your handler will not be invoked for functions or constants
that aren't in the autoload map and have the same name as a definition in the global namespace. Defaults to none.
- `"failureHandler:" classname<Facebook\AutoloadMap\FailureHandler>` - use the specified class to handle definitions that aren't the Map. Defaults to none.
- `"devFailureHandler": classname<Facebook\AutoloadMap\FailureHandler>` - use a different handler for development environments. Defaults to the same value as `failureHandler`.
- `"parser:" any of [ext-factparse]"` - select a parser to use, but there is only one valid option. Defaults to a sensible parser.
- `"useFactsIfAvailable": false` - use ext-facts (HH\Facts\...) to back Facebook\AutoloadMap\Generated\map() instead of a codegenned dict. See _Use with HH\Facts_ for more information about this mode.

Use In Development (Failure Handlers)
=====================================
Expand Down Expand Up @@ -90,12 +92,24 @@ Information you may need is available from:
- `Facebook\AutoloadMap\Generated\root()`: the directory containing the
project root, i.e. the parent directory of `vendor/`

Use with HH\\Facts
=================

HHVM 4.109 introduced ext-facts and ext-watchman. Unlike the static pre-built autoloader which is built into a [repo authoratative](https://docs.hhvm.com/hhvm/advanced-usage/repo-authoritative) build, this native autoloader works incrementally and is suitable for autoloading in your development environment. For more information about setting up this autoloader, see the [blog post](https://hhvm.com/blog/2021/05/11/hhvm-4.109.html) for hhvm 4.109.

When using a native autoloader (either the repo auth or ext-facts autoloader), you do not need hhvm-autoload to require classes/functions/types/constants at runtime. If you (and your vendor dependencies) do not call any functions in the `Facebook\AutoloadMap` namespace, other than `Facebook\AutoloadMap\initialize()`, you don't need hhvm-autoload anymore. In that case, you could drop this dependency and remove the calls to `initialize()`. If you are using other functions, like `Facebook\AutoloadMap\Generated\map()`, you'd still need the vendor/autoload.hack file that hhvm-autoload generates.

Hhvm-autoload supports outputting a vendor/autoload.hack file which forwards all queries to ext-facts. `Facebook\AutoloadMap\Generated\map_uncached()` will always be up to date in this mode, since `HH\Facts` is always up to date. `Facebook\AutoloadMap\Generated\map()` is memoized (within a request), since some code may rely on getting the same result from multiple calls. You can enable this mode by adding `"useFactsIfAvailable": true` to the hh_autoload.json config file. Hhvm-autoload will emit a shim file instead of a full map. This option is ignored if `HH\Facts\enabled()` returns false, or when `--no-facts` is passed to `vendor/bin/hh-autoload`. We recommend passing `--no-facts` when building for production (specifically repo auth mode). Returning a hardcoded dict is faster than asking `HH\Facts`.

Important to note. Autoloading with a native autoloader does not respect hh_autoload.json. The repo auth autoloader allows any code to use any symbol. The facts autoloader honors the configuration in .hhvmconfig.hdf instead. Make sure that the configuration in hh_autoload.json and .hhvmconfig.hdf match.

How It Works
============

- A parser (FactParse) provides a list of all Hack definitions in the specified locations
- This is used to generate something similar to a classmap, except including other kinds of definitions
- The map is provided to HHVM with [`HH\autoload_set_paths()`](https://docs.hhvm.com/hack/reference/function/HH.autoload_set_paths/)
- If a native autoloader is registered, this autoloader will intentionally not register itself. So calling `Facebook\AutoloadMap\initialize()` in repo auth mode or when the facts based autoloader is registered is a noop.

Contributing
============
Expand Down
50 changes: 36 additions & 14 deletions bin/hh-autoload.hack
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use namespace HH\Lib\Vec;
final class GenerateScript {
const type TOptions = shape(
'dev' => bool,
'no-facts' => bool,
);

private static function initBootstrapAutoloader(): void {
Expand Down Expand Up @@ -94,22 +95,28 @@ final class GenerateScript {
private static function parseOptions(vec<string> $argv): self::TOptions {
$options = shape(
'dev' => true,
'no-facts' => false,
);
$bin = $argv[0];
$argv = Vec\slice($argv, 1);
foreach ($argv as $arg) {
if ($arg === '--no-dev') {
$options['dev'] = false;
continue;
}
if ($arg === '--help') {
self::printUsage(\STDOUT, $bin);
exit(0);
switch ($arg) {
case '--no-dev':
$options['dev'] = false;
break;
case '--no-facts':
$options['no-facts'] = true;
break;
case '--help':
self::printUsage(\STDOUT, $bin);
exit(0);
default:
\fprintf(\STDERR, "Unrecognized option: '%s'\n", $arg);
self::printUsage(\STDERR, $bin);
exit(1);
}
\fprintf(\STDERR, "Unrecognized option: '%s'\n", $arg);
self::printUsage(\STDERR, $bin);
exit(1);
}

return $options;
}

Expand Down Expand Up @@ -139,22 +146,37 @@ final class GenerateScript {
$options['dev'] ? IncludedRoots::DEV_AND_PROD : IncludedRoots::PROD_ONLY,
);

$config = $importer->getConfig();

$handler = $options['dev']
? ($importer->getConfig()['devFailureHandler'] ?? null)
: ($importer->getConfig()['failureHandler'] ?? null);
? $config['devFailureHandler']
: $config['failureHandler'];

$emit_facts_forwarder_file = $config['useFactsIfAvailable'] &&
!$options['no-facts'];

(new Writer())
->setBuilder($importer)
->setRoot(\getcwd())
->setRelativeAutoloadRoot($importer->getConfig()['relativeAutoloadRoot'])
->setRelativeAutoloadRoot($config['relativeAutoloadRoot'])
->setFailureHandler(/* HH_IGNORE_ERROR[4110] */ $handler)
->setIsDev($options['dev'])
->setEmitFactsForwarderFile($emit_facts_forwarder_file)
->writeToDirectory(\getcwd().'/vendor/');
print(\getcwd()."/vendor/autoload.hack\n");
}

private static function printUsage(resource $to, string $bin): void {
\fprintf($to, "USAGE: %s [--no-dev]\n", $bin);
\fprintf(
$to,
"USAGE: %s [--no-dev] [--no-facts]\n".
"See the README for full information.\n".
"The README can be found at:\n\t- %s\n\t- %s.\n",
$bin,
\getcwd().'/vendor/hhvm/hhvm-autoload/README.md',
// ^^^^^^ Not accurate if hhvm-autoload is the top level project.
'https://github.com/hhvm/hhvm-autoload/blob/master/README.md',
);
}

private static function getFileList(string $root): vec<string> {
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
],
"require": {
"composer-plugin-api": "^1.0|^2.0",
"hhvm": "^4.67",
"hhvm": "^4.109",
"hhvm/hsl": "^4.0"
},
"require-dev": {
Expand Down
1 change: 1 addition & 0 deletions src/Config.hack
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ type Config = shape(
'failureHandler' => ?string,
'devFailureHandler' => ?string,
'relativeAutoloadRoot' => bool,
'useFactsIfAvailable' => bool,
);
10 changes: 10 additions & 0 deletions src/ConfigurationLoader.hack
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ abstract final class ConfigurationLoader {
'devFailureHandler',
) ??
$failure_handler,
'useFactsIfAvailable' => (
TypeAssert\is_nullable_bool(
$data['useFactsIfAvailable'] ?? null,
'useFactsIfAvailable',
) ??
false
) &&
/* HH_FIXME[2049] Facts landed in 4.109, but the hhi landed in 4.118 */
/* HH_FIXME[4107] Facts landed in 4.109, but the hhi landed in 4.118 */
\HH\Facts\enabled(),
);
}

Expand Down
40 changes: 35 additions & 5 deletions src/Writer.hack
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ final class Writer {
private bool $relativeAutoloadRoot = true;
private ?string $failureHandler;
private bool $isDev = true;
private bool $emitFactsForwarderFile = false;

/** Mark whether we're running in development mode.
*
Expand Down Expand Up @@ -55,6 +56,11 @@ final class Writer {
return $this;
}

public function setEmitFactsForwarderFile(bool $should_forward): this {
$this->emitFactsForwarderFile = $should_forward;
return $this;
}

/** Set the files and maps from a builder.
*
* Convenience function; this is equivalent to calling `setFiles()` and
Expand Down Expand Up @@ -166,9 +172,30 @@ final class Writer {
true,
);

$map = \var_export($map, true)
|> \str_replace('array (', 'dict[', $$)
|> \str_replace(')', ']', $$);
if (!$this->emitFactsForwarderFile) {
$memoize = '';
$map_as_string = \var_export($map, true)
|> \str_replace('array (', 'dict[', $$)
|> \str_replace(')', ']', $$);
} else {
$memoize = "\n<<__Memoize>>";
$map_as_string = <<<'EOF'
dict[
/* HH_FIXME[2049] Facts landed in 4.109, but the hhi landed in 4.118 */
/* HH_FIXME[4107] Facts landed in 4.109, but the hhi landed in 4.118 */
'class' => \HH\Lib\Dict\map_keys(\HH\Facts\all_types(), \HH\Lib\Str\lowercase<>),
/* HH_FIXME[2049] Facts landed in 4.109, but the hhi landed in 4.118 */
/* HH_FIXME[4107] Facts landed in 4.109, but the hhi landed in 4.118 */
'function' => \HH\Lib\Dict\map_keys(\HH\Facts\all_functions(), \HH\Lib\Str\lowercase<>),
/* HH_FIXME[2049] Facts landed in 4.109, but the hhi landed in 4.118 */
/* HH_FIXME[4107] Facts landed in 4.109, but the hhi landed in 4.118 */
'type' => \HH\Lib\Dict\map_keys(\HH\Facts\all_type_aliases(), \HH\Lib\Str\lowercase<>),
/* HH_FIXME[2049] Facts landed in 4.109, but the hhi landed in 4.118 */
/* HH_FIXME[4107] Facts landed in 4.109, but the hhi landed in 4.118 */
'constants' => \HH\Facts\all_constants(),
]
EOF;
}

if ($this->relativeAutoloadRoot) {
try {
Expand Down Expand Up @@ -206,9 +233,12 @@ function is_dev(): bool {
return (bool) \$override;
}
function map_uncached(): \Facebook\AutoloadMap\AutoloadMap {
return $map_as_string;
}
$memoize
function map(): \Facebook\AutoloadMap\AutoloadMap {
/* HH_IGNORE_ERROR[4110] invalid return type */
return $map;
return map_uncached();
}
} // Generated\
Expand Down

0 comments on commit 0b5be45

Please sign in to comment.