Skip to content

[native_assets_cli] Protocol environment variables #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dcharkes opened this issue May 3, 2023 · 1 comment · Fixed by #1764
Closed

[native_assets_cli] Protocol environment variables #32

dcharkes opened this issue May 3, 2023 · 1 comment · Fixed by #1764
Assignees
Labels
P2 A bug or feature request we're likely to work on package:hooks

Comments

@dcharkes
Copy link
Collaborator

dcharkes commented May 3, 2023

Leaning towards passing in all environment variables, and reinvoking the hooks on any environment variable change.

  • This needs to be documented somewhere.

Problem

With what environment variables should we invoke build.dart?

build.dart is invoked with a BuildConfig (which is read from args/env/file), but the CLI itself most likely invokes other processes (for example a C compiler). These processes are influenced by which environment variables are set.

Minimum env variables

Let's say we get rust_compiler package that tries to find rustc on PATH, then we must at least pass through PATH on our build.dart invocation.

On Windows, various tools rely on a host of other environment variables as well:

  • SYSTEMROOT is required for cl.exe from MSVC
  • TMP and TEMP are required for cl.exe in some cases in order to be able to write temporary files.

So the minimum list of environment variables we should pass through are the ones mentioned above.

Maximum environment variables

Conversely, there are also environment variables which are conflicting with the Dart ecosystem and native tooling.

  • SDKROOT is set by tools/test.py which breaks xcodebuild invocations as it interprets the SDKROOT as a path for the iOS or MacOS SDK rather than the Dart SDK.

Caching correctness and cache misses

If any of the environment variables influences the build, it could throw off caching of builds:

  • If we ignore all environment variables when determining whether a cache can be reused, we might accidentally not rebuild leading to stale builds. (Currently the only way for a user to fix that is to find the folder in which the build has happened and delete it.)
  • If we take all environment variables into account when determining if a cache can be reused, we might throw away build caches too often.

Possible solutions

We could consider listing the environment variables we pass through, making an allow list.
Or we could consider listing the environment variables we use for caching, but passing through all except for a deny list.

1. SDK: Pass through everything except deny list + use these for caching

Pros:

  • Correct caching
  • Users using new environment variables not blocked

Cons:

  • We might throw out valid cached for spurious environment changes.

(We should probably change SDKROOT to DARTSDKROOT in test.py and it's users.)

Even when implementing the env-filtering in the Dart and Flutter SDK, we should probably make the deny list (or allow list) part of this package, so that this package contains the whole protocol spec.

2. SDK: Pass through only an allow list + use these for caching

Pros:

  • Correct caching
  • Efficient caching

Cons:

  • If users want rely on a new environment variable, they need make an SDK PR and wait for the next Dart SDK release.

3. SDK: Pass through everything except for deny list + use allowlist for caching

Pros:

  • Users using new environment variables not blocked
  • Efficient caching

Cons:

  • Potentially incorrect caching. Users would need to make a PR to the Dart SDK to make it work.

4. package:native_assets_cli solution

Always just invoke build.dart with all environment variables, but add the allowList or denyList to package:native_assets_cli.

There would be an .environment getter on BuildConfig likely.

Pro:

  • Updating allowList/denyList does not require Dart SDK / Flutter updates.
  • We could potentially Isolate.spawnUri to run build rather than Process.run (but that would mean no exit(int) calling in any build.dart script, and tougher handling of unhandled exceptions etc.) [native_assets_cli] Protocol exit codes / unhandled exceptions #33

Con:

  • Every process invocation in build.dart requires explicitly passing environment and includeParentEnvironment: false.
  • Incorrect caching until that version of native_assets_cli rolls into the Dart SDK.

Maybe there are solutions I haven't thought about yet. I'm open to suggestions. (Otherwise I might lean towards solution 1 or 2.)

internal design doc with some initial discussion about the env vars

cc @mkustermann @liamappelbe @HosseinYousefi

@dcharkes dcharkes added the P2 A bug or feature request we're likely to work on label May 3, 2023
copybara-service bot pushed a commit to dart-lang/sdk that referenced this issue May 15, 2023
This package contains the logic for building native assets.

This package is the backend that invokes toplevel `build.dart` scripts.
For more info on these scripts see https://github.com/dart-lang/native.

This is a separate package so that dartdev and flutter_tools can reuse
the same logic without flutter_tools having to import dartdev.

Some design decisions:

* We don't yet have `build_dependencies`, so we use the ordinary
  dependency graph for ordering of native assets builds. (If there is
  a cycle we refuse to run.)
  Bug: dart-lang/pub#3794
* Builds are cached based on all the configuration provided by the
  caller. Environment variables are ignored in caching. This CL also
  contains a unit test that invokes the build by not passing through
  environment variables. However, for Windows we need to pass through
  at least `SYSTEMROOT` for MSVC to run correctly. So we might need
  to further explore if we can/want to lock env variables down.
  Bug: dart-lang/native#32
  Bug: dart-lang/native#33

Run tests:
```
dart tools/generate_package_config.dart && \
tools/test.py -n unittest-asserts-release-linux pkg/native_assets_builder
```

Bug: #50565
Change-Id: I133052d7195373e87d20924d61e1e96e3d34ce8f
Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-mac-release-try,pkg-win-release-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/300203
Reviewed-by: Liam Appelbe <[email protected]>
Commit-Queue: Daco Harkes <[email protected]>
Reviewed-by: Martin Kustermann <[email protected]>
Reviewed-by: Hossein Yousefi <[email protected]>
@dcharkes dcharkes added this to the Native Assets v1.0 milestone Aug 30, 2024
@dcharkes dcharkes self-assigned this Nov 27, 2024
@dcharkes dcharkes moved this to In Progress in Native Assets Nov 27, 2024
auto-submit bot pushed a commit that referenced this issue Nov 28, 2024
This PR changes the caching behavior for hooks to be file content hashing instead of last modified timestamps.

Closes: #1593.

In addition to using file hashes, a timestamp is passed in to detect if files were modified during a build. The moment of hashing contents is after a build is finished, but we should consider files changed after the build started to invalidate the build. If this happens, the build succeeds, but the cache is invalidated and a warning is printed to the logger.

The implementation was modeled after the [`FileStore` in flutter_tools](https://github.com/flutter/flutter/blob/1e824af6bd217beb0cc201394c9832316b6150da/packages/flutter_tools/lib/src/build_system/file_store.dart). However, it was adapted to support caching directories and to match the code style in this repository.

Directory caching is defined as follows: the hash of the names of the children. This excludes recursive descendants, and excludes the contents of children. For recursive caching (and glob patterns), the populator of the cache should do the glob/recursion to add all directories and files.

### Testing

* The existing caching tests (such as `pkgs/native_assets_builder/test/build_runner/build_runner_caching_test.dart`) cover caching behavior.
* Now that last-modified are no longer used, some sleeps have been removed from tests. � 

### Performance

Adding a stopwatch to pkgs/native_assets_builder/test/build_runner/build_runner_caching_test.dart for the second invocation of the hooks so that it is cached. 

* `lastModified` timestamps: 0.028 seconds (pre this PR)
* `package:crypto` `md5`: 0.047 seconds (current PR)
* `package:xxh3` `xxh3`: 0.042 seconds

The implementation does not use parallel system IO for loading files (no `Future.wait`), but does use async I/O to allow flutter_tools to run other `Target`s in parallel.

The (pre and post this PR) implementation is fast enough for a handful of packages with native assets in a `flutter run`. The implementation (pre and post this PR) is not fast enough for hot restart / hot reload with 10+ packages with native assets. So, when exploring support for that, we'll need to revisit the implementation.

### Related issues not addressed in this PR

* #32
  * Changing environment variables should also cause the hook to rerun.
* #1578
  * Hook compilation can potentially be shared. (This requires taking more directory locks due to concurrent and re-entrant invocations.)
@dcharkes
Copy link
Collaborator Author

dcharkes commented Nov 28, 2024

We should strongly consider having

  • includeParentEnvironment: false
  • have a minimal environment for the subprocess
  • allow embedder to add extra environment variables

This avoids hooks to accidentally depend on things they shouldn't and would be a step towards a more hermetic/sandboxed build.

(Environment variables may expose also secrets - e.g. various access tokens / api keys / ... that we may not want to blindly pass to all the hook invocations)

Originally posted by @mkustermann in #1759 (comment)

Let's take this discussion to where we already have context.

  • includeParentEnvironment: false
  • have a minimal environment for the subprocess

I've been playing around a bit with restricting the environment variables:

  static const _environmentVariablesFilter = {
    'ANDROID_HOME',
    'PATH',
    'PROGRAMDATA',
    'SYSTEMROOT',
    'TEMP',
    'TMP',
  };

That's the list of env vars needed to pass the tests on the CI which invoke clang, msvc, and the NDK.

Invalidating caching based on the full set of env vars seems overly cautious, as those might change often @mosuem

For example, these can cause a rebuild when not needed:

CHOCOLATEYLASTPATHUPDATE 133062298470192620
VSCODE_GIT_IPC_HANDLE \\.\pipe\vscode-git-db44fa1249-sock

This would probably cause a rebuild when restarting vscode, and a rebuild when updating (unrelated) packages.

  • allow embedder to add extra environment variables

@mkustermann The embedder? Or the user? Probably the user should be able to that, otherwise it would still require PRs on the embedder. But I'm slightly worried that that will make the CLI for dart run // flutter run etc quite nasty. Listing out a bunch of allowed environment variable keys.

If we want to allowlist, how much should we add?

  static const _environmentVariablesFilter = {
    // minimum to pass testing
    'ANDROID_HOME',
    'PATH',
    'PROGRAMDATA',
    'SYSTEMROOT',
    'TEMP',
    'TMP',
    // used in our own code to locate compilers (not exercised on GitHub CI)
    'HOME',
    'USER_PROFILE',
    // more of the same
    'ANDROID_AVD_HOME',
    'HOMEDRIVE',
    'HOMEPATH',
    'SYSTEMDRIVE',
    'SYSTEMROOT',
    'TMPDIR', // Otherwise Directory.systemTemp defaults to `/tmp` on Unix / MacOS
    'USER',
    'USERNAME',
    // flutter already does code signing, so we could skip?
    'CODE_SIGN_IDENTITY', 
    // more access to things on Windows, who knows what things on Windows depend on.
    'ALLUSERSPROFILE',
    'APPDATA',
    'COMMONPROGRAMFILES',
    'COMMONPROGRAMFILES(X86)',
    'COMMONPROGRAMW6432',
    'LOCALAPPDATA',
    'PROGRAMDATA',
    'PROGRAMFILES',
    'PROGRAMFILES(X86)',
    'PROGRAMW6432',
    'WINDIR',
    // other compilers
    'EMSDK_NODE',
    'EMSDK_PYTHON',
    'EMSDK',
    'NVM_BIN',
    'NVM_CD_FLAGS',
    'NVM_DIR',
    'NVM_INC',
    // access to the Dart and Flutter SDK?
    'DART_SDK_PATH',
    'DART',
    'FLUTTER_ROOT',
  };

We probably want to pass in TMP, TEMP, and TMPDIR but not invalidate the cache for these. The temp-dirs don't actually seem to change on MacOS/Windows, and Linux doesn't have one set by default.

auto-submit bot pushed a commit that referenced this issue Dec 4, 2024
…ge (#1759)

This PR makes the native assets builder save the `Platform.environment` a hook is run in. Subsequent invocations check if the environment didn't change.

This removes the `includeParentEnvironment` parameter everywhere. It was always set to true in `dartdev` and `flutter_tools` (This will require a manual roll into the Dart SDK and Flutter tools.)

A follow up PR should restrict the list of environment variables (#1764), this PR is about caching correctness.

Bug: 

* #32
@auto-submit auto-submit bot closed this as completed in 7e9897b Dec 4, 2024
@github-project-automation github-project-automation bot moved this from In Progress to Done in Native Assets Dec 4, 2024
github-merge-queue bot pushed a commit to flutter/flutter that referenced this issue Jan 6, 2025
Follow up of #160672 to filter
the environment keys being passed in to build hooks.

~~Failures don't reproduce locally, so this is a draft PR to see which
environment keys are needed for the tests to succeed on CI.~~

Edit: Fixed issues by stopping to use the
Flutter-self-update-dart-executable and instead using the
Dart-sdk-dart-executable.

Relevant bug (already addressed in Dart standalone).

* dart-lang/native#32

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P2 A bug or feature request we're likely to work on package:hooks
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

1 participant