Skip to content
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

flutter: Packaging the Flutter engine #201574

Closed
hacker1024 opened this issue Nov 17, 2022 · 13 comments · Fixed by #212328
Closed

flutter: Packaging the Flutter engine #201574

hacker1024 opened this issue Nov 17, 2022 · 13 comments · Fixed by #212328
Labels
0.kind: packaging request Request for a new package to be added

Comments

@hacker1024
Copy link
Member

hacker1024 commented Nov 17, 2022

Project description
At the moment, the Flutter package uses the prebuilt engine from Google, downloaded by the SDK.
This is not ideal, as prebuilt engine artifacts require patching for Nix(OS) compatibility.

It'd be great if we could build the Flutter engine from source.

Useful references:

Metadata

(cc: @mkg20001 @dotlambda)

@hacker1024 hacker1024 added the 0.kind: packaging request Request for a new package to be added label Nov 17, 2022
@hacker1024
Copy link
Member Author

hacker1024 commented Nov 17, 2022

I'm running into issues with Google's gclient syncing tool. It's configured to use SSH remotes, but GitHub do not allow anonymous SSH clones.

Does anyone know of a way to work around this problem?

Turns out it's just the example .gclient file that uses SSH, the Flutter repos all use HTTPS. HTTPS URLs work as long as cacert is available (#64212 (comment)).

What I have so far
{ stdenv
, lib
, fetchgit
, fetchFromGitHub
, writeText
, writeShellScriptBin
, python310
, git
, openssh
}:
let
  depot_tools = fetchgit {
    url = "https://chromium.googlesource.com/chromium/tools/depot_tools.git";
    rev = "25cf78395cd77e11b13c1bd26124e0a586c19166";
    sha256 = "sha256-Qn0rqX2+wYpbyfwYzeaFsbsLvuGV6+S9GWrH3EqaHmU=";
  };
  gclient = writeShellScriptBin "gclient" ''
    ${python310.withPackages (py: with py; [ google-auth-httplib2 ])}/bin/python ${depot_tools}/gclient.py "$@"
  '';
  ssh = writeShellScriptBin "ssh" ''
    if [ ! -f id_rsa ]; then
      ${openssh}/bin/ssh-keygen -t rsa -N "" -f id_rsa > /dev/null
    fi
    ${openssh}/bin/ssh -i id_rsa.pub -o StrictHostKeyChecking=no "$@"
  '';

  src = stdenv.mkDerivation rec {
    pname = "flutter-engine-sources";
    version = "857bd6b74c5eb56151bfafe91e7fa6a82b6fee25";

    nativeBuildInputs = [ gclient git ssh ];

    unpackPhase = ''
      runHook preUnpack

      mkdir engine && cd engine
      ln -s ${
        writeText ".gclient" ''
          solutions = [
            {
              "managed": False,
              "name": "src/flutter",
              "url": "[email protected]:flutter/engine.git@${version}",
              "custom_deps": {},
              "deps_file": "DEPS",
              "safesync_url": "",
            },
          ]
        ''
      } .gclient
      gclient sync

      runHook postUnpack
    '';

    outputHashAlgo = "sha256";
    outputHash = lib.fakeSha256;
  };
in
stdenv.mkDerivation {
  pname = "flutter-engine";
  inherit (src) version;

  inherit src;
}

@hacker1024 hacker1024 changed the title Packaging the Flutter engine flutter: Packaging the Flutter engine Nov 17, 2022
@milahu
Copy link
Contributor

milahu commented Nov 22, 2022

This is not ideal, as prebuilt engine artifacts require patching for Nix(OS) compatibility.

what exactly is the benefit of a source build?
flutter is a giant project and takes forever to build

misleading, we dont need push access

we just need
https://github.com/flutter/flutter/wiki/Compiling-the-engine#compiling-for-macos-or-linux

a bit cleaned up

/*
https://github.com/NixOS/nixpkgs/issues/201574
*/

{ stdenv
, lib
, fetchgit
, fetchFromGitHub
, writeShellScriptBin
, python3
, git
, cacert
, ninja
}:

let
  depot_tools = fetchgit {
    url = "https://chromium.googlesource.com/chromium/tools/depot_tools";
    rev = "30e3ce8b1c670be00c4957fe773ffb8ff986ed8f";
    sha256 = "sha256-R9CX/xzvLiOVQmAsviTFUFv6DiStxa0b6O0zHNaH6SY=";
  };
  gclient = writeShellScriptBin "gclient" ''
    ${python3.withPackages (py: with py; [ google-auth-httplib2 ])}/bin/python ${depot_tools}/gclient.py "$@"
  '';
in

stdenv.mkDerivation rec {
  pname = "flutter-engine";
  version = "981fe92ab998d655abded58f1f0ef2a8daeadd02";

  # https://github.com/flutter/engine
  src = fetchFromGitHub {
    owner = "flutter";
    repo = "engine";
    rev = version;
    sha256 = "sha256-9uY7Q8ZR51vB7vsjEPGAWyvhOKRfdj5B8o0KwypwdTY=";
  };

  # only fetching the sources takes 1 hour ...
  deps = stdenv.mkDerivation {
    inherit src;
    name = "${pname}-${version}-deps";
    nativeBuildInputs = [
      gclient
      git
      cacert # fix: error:16000069:STORE routines::unregistered scheme
    ];
    outputHashAlgo = "sha256";
    outputHashMode = "recursive";
    outputHash = ""; # TODO
    # TODO copy less? copy only some folders, then merge with source
    buildPhase = ''
      (
        set -x
        gclient config https://github.com/flutter/engine
        gclient sync --revision engine@${version} --shallow --verbose
        cp -r . $out
      )
    '';
  };

  postUnpack = ''
    (
      cd $sourceRoot
      echo TODO merge with source. copy or symlink ${deps}
      exit 1
    )
  '';

  nativeBuildInputs = [
    ninja
  ];

  /*
    TODO
    https://github.com/flutter/flutter/wiki/Compiling-the-engine#compiling-for-macos-or-linux
    this will take forever ...
  */
  buildPhase = ''
    ./flutter/tools/gn --unoptimized # to prepare your build files.
    ninja -C out/host_debug_unopt # to build a desktop unoptimized binary.
  '';
}

this currently throws

FileNotFoundError: [Errno 2] No such file or directory: 'cipd'
...
[0:50:21] Finished running: git checkout --quiet bea8d2471bd912220ba59032e0738f3364632657
[0:50:21] Finished running: git rev-parse --abbrev-ref=strict HEAD
[0:50:21] Checked out bea8d2471bd912220ba59032e0738f3364632657 to a detached HEAD. Before making any commits
in this repo, you should use 'git checkout <branch>' to switch 
to an existing branch or use 'git checkout origin -b <branch>' to
create a new branch for your work.
[0:50:21] Finished running: git -c core.quotePath=false ls-files
[0:50:21] Finished running: git rev-parse --verify HEAD
[0:50:21] Finished running: git rev-parse HEAD
[0:50:21] Finished.
----------------------------------------
Traceback (most recent call last):
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/subprocess2.py", line 158, in __init__
    super(Popen, self).__init__(args, **kwargs)
  File "/nix/store/xcaaly5shfy227ffs8nipxrd49b56iqq-python3-3.10.8/lib/python3.10/subprocess.py", line 971, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/nix/store/xcaaly5shfy227ffs8nipxrd49b56iqq-python3-3.10.8/lib/python3.10/subprocess.py", line 1847, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'cipd'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/metrics.py", line 291, in print_notice_and_exit
    yield
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/gclient.py", line 3489, in <module>
    sys.exit(main(sys.argv[1:]))
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/gclient.py", line 3475, in main
    return dispatcher.execute(OptionParser(), argv)
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/subcommand.py", line 252, in execute
    return command(parser, args[1:])
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/gclient.py", line 3026, in CMDsync
    ret = client.RunOnDeps('update', args)
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/gclient.py", line 2057, in RunOnDeps
    self._cipd_root.run(command)
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/gclient_scm.py", line 1638, in run
    self.ensure()
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/gclient_scm.py", line 1633, in ensure
    gclient_utils.CheckCallAndFilter(
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/gclient_utils.py", line 643, in CheckCallAndFilter
    kid = subprocess2.Popen(
  File "/nix/store/hz51dw0z29igm0ic3ka67ya5izjkyp4r-depot_tools-30e3ce8/subprocess2.py", line 172, in __init__
    raise OSError('Execution failed with error: %s.\n'
OSError: Execution failed with error: [Errno 2] No such file or directory: 'cipd'.
Check that None or cipd exist and have execution permission.

cipd is part of https://chromium.googlesource.com/chromium/tools/depot_tools

@nathaniel-brough
Copy link
Contributor

I came here as I'm currently looking to package google/pigweed using nix (via a search for cipd). I think cipd can be built from source relatively easily and could potentially be packaged in nix itself. e.g.

go get go.chromium.org/luci/cipd/client/cipd

Will fetch and build cipd.

@milahu
Copy link
Contributor

milahu commented Nov 29, 2022

building cipd with buildGoModule is like 20 lines of nix code
but so far we dont need cipd in nixpkgs

https://ryantm.github.io/nixpkgs/languages-frameworks/go/
https://nixos.wiki/wiki/Go

@hacker1024
Copy link
Member Author

I didn't realize that cipd was a simple Go program, but I did notice that it was statically linked.

I've been retrieving it by parsing the depot_tools version metadata like so:

let
  depot_tools = fetchgit {
    url = "https://chromium.googlesource.com/chromium/tools/depot_tools.git";
    rev = "25cf78395cd77e11b13c1bd26124e0a586c19166";
    sha256 = "sha256-Qn0rqX2+wYpbyfwYzeaFsbsLvuGV6+S9GWrH3EqaHmU=";
  };
  cipd =
    let
      cipdVersion = builtins.readFile "${depot_tools}/cipd_client_version";
      cipdHashes = builtins.listToAttrs (
        map
          (line:
            let
              segments = builtins.split " +" line;
            in
            { name = builtins.head segments; value = lib.last segments; })
          (lib.filter (line: !(lib.hasPrefix "#" line || line == "")) (lib.splitString "\n" (builtins.readFile "${depot_tools}/cipd_client_version.digests")))
      );
      cipdPlatform =
        if stdenv.isDarwin then
          (if stdenv.isAarch64 then "mac-arm64" else "mac-amd64")
        else
          (if stdenv.isAarch64 then "linux-arm64" else "linux-amd64");
      cipdBinary = fetchurl {
        name = "cipd-${cipdPlatform}-${cipdVersion}-unwrapped";
        url = "https://chrome-infra-packages.appspot.com/client?platform=${cipdPlatform}&version=${cipdVersion}";
        sha256 = cipdHashes.${cipdPlatform};
      };
    in
    runCommandLocal "cipd-${cipdPlatform}-${cipdVersion}" { } "mkdir -p $out/bin; install -m 755 ${cipdBinary} $out/bin/cipd";
  gclient = writeShellScriptBin "gclient" ''
    ${python310.withPackages (py: with py; [ google-auth-httplib2 ])}/bin/python ${depot_tools}/gclient.py "$@"
  '';
in
# ...

@hacker1024
Copy link
Member Author

what exactly is the benefit of a source build?
flutter is a giant project and takes forever to build

The Flutter SDK fetches many executable components from Google's servers at runtime. These all have FHS dependencies.

By building the Flutter SDK manually, we can make sure everything is linked to Nix store libraries and is available offline.

Other alternatives would be to do one of the following:

  • Patch the Flutter SDK to patch executables with patchelf after downloading them (this can be patched without building as this is done entirely in JIT-compiled Dart part of the SDK)
  • Download every runtime component and patch it when building the derivation

The former seems tricky to implement, and the latter requires somehow curating a list of all external components. I don't think these are enumerated in any one place.

A FHS user environment is sub-optimal, because parts of the Flutter engine are copied into apps built with Flutter. This would mean that all packaged Flutter apps would have to be patched with patchelf, even if they're built from source, which really should not be necessary.

@milahu
Copy link
Contributor

milahu commented Nov 29, 2022

The Flutter SDK fetches many executable components from Google's servers at runtime.

thats bad, but does not justify a source build.
we can prefetch all these binaries on build time

By building the Flutter SDK manually, we can make sure everything is linked to Nix store libraries

gclient spends ONE HOUR just to fetch all the sources ...
so ideally, you will do a split build (one nix derivation per node in the build graph)
maybe overriding some packages with cached versions from nixpkgs (to save build time)

i collected some links in https://nixos.wiki/wiki/Gn

im not saying that source build is a bad idea
im just warning you that it could be a lot of work (developer time + cpu time)

@RossComputerGuy
Copy link
Member

RossComputerGuy commented Jan 20, 2023

I've been working on this and was able to build the embedded engine on host but not inside of Nix. Yesterday, I managed to pull all dependencies and "lock" them in with Nix. Currently working on trying to build the actual engine in Nix.

My Nix files for Flutter's Engine

@RossComputerGuy
Copy link
Member

I managed to successfully compile the embedded engine completely in Nix. I've got issues with reproduction because of gclient's requirement for having .git folders. So I've decided to ditch gclient and fake doing what it does. I'll probably have a PR out next month with the essentials for a working Flutter Engine build.

@hacker1024
Copy link
Member Author

IIRC there is a way to make .git directories reproducible, I'll see if I can find what I've done before soon.

@dotlambda
Copy link
Member

IIRC there is a way to make .git directories reproducible, I'll see if I can find what I've done before soon.

leaveDotGit is known to cause reproducability issues.

@RossComputerGuy
Copy link
Member

Yes, and that's why I am working on being able to build without leaveDotGit. I've almost got it working. Currently running into this error:

flutter-engine-jit_release> unpacking sources
flutter-engine-jit_release> unpacking source archive /nix/store/4c7vpcdgi62ss7231asy36sppmsql1z6-flutter-engine-src-857bd6b74c5eb56151bfafe91e7fa6a82b6fee25
flutter-engine-jit_release> source root is flutter-engine-src-857bd6b74c5eb56151bfafe91e7fa6a82b6fee25
flutter-engine-jit_release> patching sources
flutter-engine-jit_release> configuring
flutter-engine-jit_release> Using prebuilt Dart SDK binary. If you are editing Dart sources and wish to compile the Dart SDK, set `--no-prebuilt-dart-sdk`.
flutter-engine-jit_release> Generating GN files in: /nix/store/ghd4arg0hga5zw1yaknrcrsb7zd8rqgr-flutter-engine-jit_release-857bd6b74c5eb56151bfafe91e7fa6a82b6fee25/lib/flutter/out/jit_release
flutter-engine-jit_release> ERROR at //third_party/dart/runtime/bin/BUILD.gn:5:1: Can't load input file.
flutter-engine-jit_release> import("../../build/config/gclient_args.gni")
flutter-engine-jit_release> ^-------------------------------------------
flutter-engine-jit_release> Unable to load:
flutter-engine-jit_release>   /build/flutter-engine-src-857bd6b74c5eb56151bfafe91e7fa6a82b6fee25/src/third_party/dart/build/config/gclient_args.gni
flutter-engine-jit_release> I also checked in the secondary tree for:
flutter-engine-jit_release>   /build/flutter-engine-src-857bd6b74c5eb56151bfafe91e7fa6a82b6fee25/src/build/secondary/third_party/dart/build/config/gclient_args.gni
flutter-engine-jit_release> See //flutter/BUILD.gn:101:9: which caused the file to be included.
flutter-engine-jit_release>         "//third_party/dart/runtime/bin:gen_snapshot",
flutter-engine-jit_release>         ^--------------------------------------------
flutter-engine-jit_release>

@RossComputerGuy
Copy link
Member

I have resolved all errors but now I am stuck with this error:

flutter-engine-jit_release> [0/1] Regenerating ninja files
flutter-engine-jit_release> [0/1] Regenerating ninja files
flutter-engine-jit_release> [0/1] Regenerating ninja files
flutter-engine-jit_release> [0/1] Regenerating ninja files
flutter-engine-jit_release> [0/1] Regenerating ninja files
flutter-engine-jit_release> [0/1] Regenerating ninja files
flutter-engine-jit_release> ninja: error: manifest 'build.ninja' still dirty after 100 tries, perhaps system time is not set

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
0.kind: packaging request Request for a new package to be added
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants