Add packages recipe support for pinned Flutter

Duplicates the recent change to the generic plugins recipe that allows
using a pinned version of Flutter instead of the latest version of a
channel.

Bug: https://github.com/flutter/flutter/issues/93811
Change-Id: If8e1dab27c8d6c8c27b1771cb5af12f758667df6
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/25460
Reviewed-by: Godofredo Contreras <godofredoc@google.com>
Commit-Queue: Stuart Morgan <stuartmorgan@google.com>
diff --git a/recipes/packages/packages.expected/basic.json b/recipes/packages/packages.expected/master_channel.json
similarity index 94%
copy from recipes/packages/packages.expected/basic.json
copy to recipes/packages/packages.expected/master_channel.json
index 8542df6..854eda9 100644
--- a/recipes/packages/packages.expected/basic.json
+++ b/recipes/packages/packages.expected/master_channel.json
@@ -5,120 +5,6 @@
   },
   {
     "cmd": [],
-    "name": "checkout source code.Checkout flutter/flutter",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::git]/resources/git_setup.py",
-      "--path",
-      "[START_DIR]/flutter",
-      "--url",
-      "https://chromium.googlesource.com/external/github.com/flutter/flutter"
-    ],
-    "name": "checkout source code.Checkout flutter/flutter.git setup",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "origin",
-      "None",
-      "--recurse-submodules",
-      "--progress",
-      "--tags"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "env": {
-      "PATH": "RECIPE_REPO[depot_tools]:<PATH>"
-    },
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.git fetch",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "-f",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.git checkout",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.read revision",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_TEXT@<br/>checked out 'deadbeef'<br/>@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"deadbeef\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "clean",
-      "-f",
-      "-d",
-      "-x"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.git clean",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "sync"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.submodule sync",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "update",
-      "--init",
-      "--recursive"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.submodule update",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
     "name": "checkout source code.Checkout flutter/packages",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
@@ -232,6 +118,139 @@
     ]
   },
   {
+    "cmd": [
+      "vpython",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/packages/.ci/flutter_master.version",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "checkout source code.read pinned version",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@STEP_LOG_LINE@flutter_master.version@refs/heads/master@@@",
+      "@@@STEP_LOG_END@flutter_master.version@@@"
+    ]
+  },
+  {
+    "cmd": [],
+    "name": "checkout source code.Checkout flutter/flutter",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[depot_tools::git]/resources/git_setup.py",
+      "--path",
+      "[START_DIR]/flutter",
+      "--url",
+      "https://chromium.googlesource.com/external/github.com/flutter/flutter"
+    ],
+    "name": "checkout source code.Checkout flutter/flutter.git setup",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "origin",
+      "master",
+      "--recurse-submodules",
+      "--progress",
+      "--tags"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "env": {
+      "PATH": "RECIPE_REPO[depot_tools]:<PATH>"
+    },
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.read revision",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_TEXT@<br/>checked out 'deadbeef'<br/>@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision@\"deadbeef\"@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
     "cmd": [],
     "name": "Dependencies"
   },
diff --git a/recipes/packages/packages.expected/basic.json b/recipes/packages/packages.expected/stable_channel.json
similarity index 99%
rename from recipes/packages/packages.expected/basic.json
rename to recipes/packages/packages.expected/stable_channel.json
index 8542df6..332a5ea 100644
--- a/recipes/packages/packages.expected/basic.json
+++ b/recipes/packages/packages.expected/stable_channel.json
@@ -5,120 +5,6 @@
   },
   {
     "cmd": [],
-    "name": "checkout source code.Checkout flutter/flutter",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@1@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "RECIPE_MODULE[depot_tools::git]/resources/git_setup.py",
-      "--path",
-      "[START_DIR]/flutter",
-      "--url",
-      "https://chromium.googlesource.com/external/github.com/flutter/flutter"
-    ],
-    "name": "checkout source code.Checkout flutter/flutter.git setup",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "fetch",
-      "origin",
-      "None",
-      "--recurse-submodules",
-      "--progress",
-      "--tags"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "env": {
-      "PATH": "RECIPE_REPO[depot_tools]:<PATH>"
-    },
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.git fetch",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "checkout",
-      "-f",
-      "FETCH_HEAD"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.git checkout",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "rev-parse",
-      "HEAD"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.read revision",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@",
-      "@@@STEP_TEXT@<br/>checked out 'deadbeef'<br/>@@@",
-      "@@@SET_BUILD_PROPERTY@got_revision@\"deadbeef\"@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "clean",
-      "-f",
-      "-d",
-      "-x"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.git clean",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "sync"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.submodule sync",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "git",
-      "submodule",
-      "update",
-      "--init",
-      "--recursive"
-    ],
-    "cwd": "[START_DIR]/flutter",
-    "infra_step": true,
-    "name": "checkout source code.Checkout flutter/flutter.submodule update",
-    "~followup_annotations": [
-      "@@@STEP_NEST_LEVEL@2@@@"
-    ]
-  },
-  {
-    "cmd": [],
     "name": "checkout source code.Checkout flutter/packages",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
@@ -233,6 +119,120 @@
   },
   {
     "cmd": [],
+    "name": "checkout source code.Checkout flutter/flutter",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[depot_tools::git]/resources/git_setup.py",
+      "--path",
+      "[START_DIR]/flutter",
+      "--url",
+      "https://chromium.googlesource.com/external/github.com/flutter/flutter"
+    ],
+    "name": "checkout source code.Checkout flutter/flutter.git setup",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "fetch",
+      "origin",
+      "stable",
+      "--recurse-submodules",
+      "--progress",
+      "--tags"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "env": {
+      "PATH": "RECIPE_REPO[depot_tools]:<PATH>"
+    },
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.git fetch",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "checkout",
+      "-f",
+      "FETCH_HEAD"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.git checkout",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "rev-parse",
+      "HEAD"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.read revision",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@",
+      "@@@STEP_TEXT@<br/>checked out 'deadbeef'<br/>@@@",
+      "@@@SET_BUILD_PROPERTY@got_revision@\"deadbeef\"@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "clean",
+      "-f",
+      "-d",
+      "-x"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.git clean",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "sync"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.submodule sync",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "git",
+      "submodule",
+      "update",
+      "--init",
+      "--recursive"
+    ],
+    "cwd": "[START_DIR]/flutter",
+    "infra_step": true,
+    "name": "checkout source code.Checkout flutter/flutter.submodule update",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@2@@@"
+    ]
+  },
+  {
+    "cmd": [],
     "name": "Dependencies"
   },
   {
diff --git a/recipes/packages/packages.py b/recipes/packages/packages.py
index 69b196e..395c683 100644
--- a/recipes/packages/packages.py
+++ b/recipes/packages/packages.py
@@ -9,6 +9,7 @@
     'flutter/repo_util',
     'flutter/yaml',
     'recipe_engine/context',
+    'recipe_engine/file',
     'recipe_engine/json',
     'recipe_engine/path',
     'recipe_engine/properties',
@@ -21,19 +22,27 @@
   packages_checkout_path = api.path['start_dir'].join('packages')
   flutter_checkout_path = api.path['start_dir'].join('flutter')
   channel = api.properties.get('channel')
+  version_file_name = api.properties.get('version_file', '')
   with api.step.nest('checkout source code'):
-    # Check out flutter ToT from master.
-    api.repo_util.checkout(
-        'flutter',
-        checkout_path=flutter_checkout_path,
-        ref='refs/heads/%s' % channel,
-    )
     api.repo_util.checkout(
         'packages',
         checkout_path=packages_checkout_path,
         url=api.properties.get('git_url'),
         ref=api.properties.get('git_ref')
     )
+    # Check out the specified version of Flutter.
+    flutter_ref = 'refs/heads/%s' % channel
+    # When specified, use a pinned version instead of latest.
+    if version_file_name:
+      version_file = packages_checkout_path.join('.ci', version_file_name)
+      flutter_ref = api.file.read_text(
+          'read pinned version', version_file, flutter_ref
+      ).strip()
+    api.repo_util.checkout(
+        'flutter',
+        checkout_path=flutter_checkout_path,
+        ref=flutter_ref,
+    )
   env, env_prefixes = api.repo_util.flutter_environment(flutter_checkout_path)
   with api.step.nest('Dependencies'):
     deps = api.properties.get('dependencies', [])
@@ -76,8 +85,20 @@
 
 
 def GenTests(api):
+  flutter_path = api.path['start_dir'].join('flutter')
   tasks_dict = {'tasks': [{'name': 'one', 'script': 'myscript'}]}
   yield api.test(
-      'basic', api.repo_util.flutter_environment_data(),
+      'master_channel', api.repo_util.flutter_environment_data(flutter_path),
+      api.properties(
+          channel='master',
+          version_file='flutter_master.version',
+      ),
+      api.step_data('read yaml.parse', api.json.output(tasks_dict))
+  )
+  yield api.test(
+      'stable_channel', api.repo_util.flutter_environment_data(flutter_path),
+      api.properties(
+          channel='stable',
+      ),
       api.step_data('read yaml.parse', api.json.output(tasks_dict))
   )