devtools/madb: handle build variants explicitly and cache the project ids.
1) Now the sub-module name and the build variant name of an Android
project can be explicitly provided with "-module" and "-variant"
flags of madb start. When these are not specified, the first
available application module and the first build variant is chosen
with a warning message.
2) The returned project ids are now kept in a cache, so that they can
be reused without running Gradle scripts again. The cache is a map
of (variantKey, projectIds) pair, and implemented in id_cache.go.
Users can choose to clear the cache and extract the ids again by
providing "-clear-cache" flag.
3) madb start now also provides "-force-stop" flag which defaults to
true. Users can set it to false if they just want to bring the
running app to the front, instead of restarting the app.
Change-Id: I4bdfd34bf7710477a4ff2af3d16c36bec8d1860d
diff --git a/doc.go b/doc.go
index 6ba1a00..0f73147 100644
--- a/doc.go
+++ b/doc.go
@@ -69,7 +69,7 @@
Launches your app on all devices.
Usage:
- madb start [flags] <application_id> <activity_name>
+ madb start [flags] [<application_id> <activity_name>]
<application_id> is usually the package name where the activities are defined.
(See:
@@ -80,7 +80,38 @@
activity name must be a fully-qualified name (e.g.,
com.yourcompany.yourapp.MainActivity).
+If either <application_id> or <activity_name> is provided, the other must be
+provided as well.
+
+If no arguments are specified, madb automatically determines which app to
+launch, based on the build scripts found in the current working directory.
+
+1) If the working directory contains a Flutter project (i.e., has
+"flutter.yaml"), this command will run "flutter start
+--android-device-id=<device serial>" for all the specified devices.
+
+2) If the working directory contains a Gradle Android project (i.e., has
+"build.gradle"), this command will run a small Gradle script to extract the
+application ID and the main activity name. In this case, the extracted IDs are
+cached, so that "madb start" can be repeated without even running the Gradle
+script again. The IDs can be re-extracted by clearing the cache by providing
+"-clear-cache" flag.
+
The madb start flags are:
+ -clear-cache=false
+ Clear the cache and re-extract the application ID and the main activity name.
+ Only takes effect when no arguments are provided.
+ -force-stop=true
+ Force stop the target app before starting the activity.
+ -module=
+ Specify which application module to use, when the current directory is the
+ top level Gradle project containing multiple sub-modules. When not
+ specified, the first available application module is used. Only takes effect
+ when no arguments are provided.
+ -variant=
+ Specify which build variant to use. When not specified, the first available
+ build variant is used. Only takes effect when no arguments are provided.
+
-d=false
Restrict the command to only run on real devices.
-e=false
diff --git a/id_cache.go b/id_cache.go
new file mode 100644
index 0000000..df72541
--- /dev/null
+++ b/id_cache.go
@@ -0,0 +1,110 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "encoding/gob"
+ "fmt"
+ "os"
+ "path/filepath"
+)
+
+// variantKey specifies a build variant in an Android Gradle project.
+type variantKey struct {
+ // Dir indicates the project directory where "build.gradle" resides.
+ Dir string
+ // Module indicates the name of the sub-module. Can be an empty string.
+ Module string
+ // Variant is the name of the build variant in an Android application module.
+ // When there are no product flavors, there are only two build variants: "debug" and "release".
+ Variant string
+}
+
+// projectIds contains the application ID and the activity name extracted from the Gradle scripts.
+type projectIds struct {
+ AppID, Activity string
+}
+
+// idCache is a map used for caching the ids extracted from the Gradle scripts, so that apps can be
+// launched more quickly without running Gradle tasks.
+type idCache map[variantKey]projectIds
+
+func getIDCache(cacheFile string) (idCache, error) {
+ return readIDCacheMap(cacheFile)
+}
+
+// Clears the cache entry from the given cacheFile.
+func clearIDCacheEntry(key variantKey, cacheFile string) error {
+ cache, err := getIDCache(cacheFile)
+ if err != nil {
+ return err
+ }
+
+ delete(cache, key)
+ return writeIDCacheMap(cache, cacheFile)
+}
+
+// Adds a new entry in the id cache located at cacheFile and save the cache back to the file.
+func writeIDCacheEntry(key variantKey, ids projectIds, cacheFile string) error {
+ cache, err := getIDCache(cacheFile)
+ if err != nil {
+ return err
+ }
+
+ cache[key] = ids
+ return writeIDCacheMap(cache, cacheFile)
+}
+
+// Reads the id cache map from the given file using gob-encoding.
+func readIDCacheMap(filename string) (idCache, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ // If the file does not exist, return an empty map without an error.
+ if os.IsNotExist(err) {
+ return idCache{}, nil
+ }
+
+ // An unexpected error occurred and should be returned.
+ return nil, err
+ }
+ defer f.Close()
+
+ decoder := gob.NewDecoder(f)
+ result := idCache{}
+
+ // Decoding might fail when the cache file is somehow corrupted, or when the cache schema is
+ // updated. In such cases, move on after resetting the cache file instead of exiting the app.
+ if err := decoder.Decode(&result); err != nil {
+ fmt.Fprintln(os.Stderr, "WARNING: Could not decode the id cache file. Resetting the cache.")
+ if err := os.Remove(f.Name()); err != nil {
+ return nil, err
+ }
+
+ return idCache{}, nil
+ }
+
+ return result, nil
+}
+
+// Writes the id cache map to the given file using gob-encoding.
+func writeIDCacheMap(cache idCache, filename string) error {
+ f, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ encoder := gob.NewEncoder(f)
+ return encoder.Encode(cache)
+}
+
+func getDefaultCacheFilePath() (string, error) {
+ configDir, err := getConfigDir()
+ if err != nil {
+ return "", err
+ }
+
+ return filepath.Join(configDir, "id_cache"), nil
+}
diff --git a/madb.go b/madb.go
index 88bc3e0..04b719c 100644
--- a/madb.go
+++ b/madb.go
@@ -160,7 +160,12 @@
// Gets all the devices specified by the device specifier flags.
// Intended to be used by most of the madb sub-commands except for 'madb name'.
func getSpecifiedDevices() ([]device, error) {
- allDevices, err := getDevices(getDefaultNameFilePath())
+ nicknameFile, err := getDefaultNameFilePath()
+ if err != nil {
+ return nil, err
+ }
+
+ allDevices, err := getDevices(nicknameFile)
if err != nil {
return nil, err
}
@@ -227,6 +232,21 @@
return false
}
+// Returns the config dir located at "~/.madb"
+func getConfigDir() (string, error) {
+ home := os.Getenv("HOME")
+ if home == "" {
+ return "", fmt.Errorf("Could not find the HOME directory.")
+ }
+
+ configDir := filepath.Join(home, ".madb")
+ if err := os.MkdirAll(configDir, 0755); err != nil {
+ return "", err
+ }
+
+ return configDir, nil
+}
+
type subCommandRunner struct {
// init is an optional function that does some initial work that should only
// be performed once, before directing the command to all the devices.
@@ -307,6 +327,41 @@
return cmd.Shell().Err
}
+type idExtractorFunc func(variantKey) (projectIds, error)
+
+// Returns the project ids for the given build variant. It returns the cached values when the
+// variant is found in the cache file, unless the clearCache argument is true. Otherwise, it calls
+// extractIdsFromGradle to extract those ids by running Gradle scripts.
+func getProjectIds(extractor idExtractorFunc, key variantKey, clearCache bool, cacheFile string) (projectIds, error) {
+ if clearCache {
+ clearIDCacheEntry(key, cacheFile)
+ } else {
+ // See if the current configuration appears in the cache.
+ cache, err := getIDCache(cacheFile)
+ if err != nil {
+ return projectIds{}, err
+ }
+
+ if ids, ok := cache[key]; ok {
+ fmt.Println("NOTE: Cached IDs are being used. Use '-clear-cache' flag to clear the cache and extract the IDs from Gradle scripts again.")
+ return ids, nil
+ }
+ }
+
+ fmt.Println("Running Gradle to extract the application ID and the main activity name...")
+ ids, err := extractor(key)
+ if err != nil {
+ return projectIds{}, err
+ }
+
+ // Write these ids to the cache.
+ if err := writeIDCacheEntry(key, ids, cacheFile); err != nil {
+ return projectIds{}, fmt.Errorf("Could not write ids to the cache file: %v", err)
+ }
+
+ return ids, nil
+}
+
func isFlutterProject(dir string) bool {
_, err := os.Stat(filepath.Join(dir, "flutter.yaml"))
return err == nil
@@ -350,7 +405,7 @@
}
// TODO(youngseokyoon): find a better way to distribute the gradle script.
-func findGradleInitScript(dir string) (string, error) {
+func findGradleInitScript() (string, error) {
jiriRoot := os.Getenv("JIRI_ROOT")
if jiriRoot == "" {
return "", fmt.Errorf("JIRI_ROOT environment variable is not set")
@@ -364,7 +419,7 @@
return initScript, nil
}
-func extractIdsFromGradle(dir string) (appID, activity string, err error) {
+func extractIdsFromGradle(key variantKey) (ids projectIds, err error) {
sh := gosh.NewShell(nil)
defer sh.Cleanup()
@@ -374,12 +429,12 @@
sh.PropagateChildOutput = true
sh.ContinueOnError = true
- wrapper, err := findGradleWrapper(dir)
+ wrapper, err := findGradleWrapper(key.Dir)
if err != nil {
return
}
- initScript, err := findGradleInitScript(dir)
+ initScript, err := findGradleInitScript()
if err != nil {
err = fmt.Errorf("Could not find the madb_init.gradle script: %v", err)
return
@@ -389,7 +444,19 @@
outputFile := sh.MakeTempFile()
// Run the gradle wrapper to extract the application ID and the main activity name from the build scripts.
- cmdArgs := []string{"--daemon", "-p", dir, "-q", "-I", initScript, "-PmadbOutputFile=" + outputFile.Name(), "madbExtractApplicationId", "madbExtractMainActivity"}
+ cmdArgs := []string{"--daemon", "-q", "-I", initScript, "-PmadbOutputFile=" + outputFile.Name()}
+
+ // Specify the project directory. If the module name is explicitly set, combine it with the base directory.
+ cmdArgs = append(cmdArgs, "-p", filepath.Join(key.Dir, key.Module))
+
+ // Specify the variant
+ if key.Variant != "" {
+ cmdArgs = append(cmdArgs, "-PmadbVariant="+key.Variant)
+ }
+
+ // Specify the tasks
+ cmdArgs = append(cmdArgs, "madbExtractApplicationId", "madbExtractMainActivity")
+
cmd := sh.Cmd(wrapper, cmdArgs...)
cmd.Run()
@@ -410,6 +477,6 @@
return
}
- appID, activity = lines[0], lines[1]
+ ids = projectIds{lines[0], lines[1]}
return
}
diff --git a/madb_init.gradle b/madb_init.gradle
index 4b6b4fd..5c1cdf0 100644
--- a/madb_init.gradle
+++ b/madb_init.gradle
@@ -29,12 +29,16 @@
def id = null
// See if this project is an Android application module.
- if (isApplicationModule(project)) {
- id = extractor(project)
- } else {
- // This project is NOT an Android application module.
- // Retrieve the id from the first application sub-module.
- id = extractor(project.subprojects.find { isApplicationModule(it) })
+ try {
+ if (isApplicationModule(project)) {
+ id = extractor(project)
+ } else {
+ // This project is NOT an Android application module.
+ // Retrieve the id from the first application sub-module.
+ id = extractor(project.subprojects.find { isApplicationModule(it) })
+ }
+ } catch (Throwable t) {
+ throw new GradleException('Failed to extract the ' + debugName + ': ' + t.message, t)
}
if (id) {
@@ -53,31 +57,45 @@
}
String getAppId(project) {
- try {
- return project.android.defaultConfig.applicationId
- } catch (all) {
- return null
+ if (project.properties.containsKey('madbVariant')) {
+ def variantName = project.properties['madbVariant']
+ def allVariants = project.android.applicationVariants
+ def matchingVariants = allVariants.matching { variantName.equalsIgnoreCase(it.name) }
+ if (matchingVariants.size() != 1) {
+ throw new GradleException('Variant "' + variantName + '" is not found.')
+ }
+
+ def targetVariant = matchingVariants.getAt(0)
+
+ def suffix = targetVariant.buildType.applicationIdSuffix
+ if (suffix == null) {
+ suffix = ""
+ }
+
+ return targetVariant.mergedFlavor.applicationId + suffix
+ } else {
+ def targetVariant = project.android.applicationVariants.getAt(0)
+ print 'Build variant not specified. '
+ println 'The first variant "' + targetVariant.name + '" is chosen automatically.'
+ println '(NOTE: Variant can be explicitly specified using -variant=<variant name> flag.)'
+ return project.android.applicationVariants.getAt(0).mergedFlavor.applicationId
}
}
String getMainActivity(project) {
- try {
- def manifestFile = getAndroidManifestLocation(project)
+ def manifestFile = getAndroidManifestLocation(project)
- // Parse the xml file and find the main activity.
- def manifest = new XmlSlurper().parse(manifestFile)
- def mainActivity = manifest.application.activity.find { isMainActivity(it) }
- def name = mainActivity.'@android:name'.text()
+ // Parse the xml file and find the main activity.
+ def manifest = new XmlSlurper().parse(manifestFile)
+ def mainActivity = manifest.application.activity.find { isMainActivity(it) }
+ def name = mainActivity.'@android:name'.text()
- // If the activity name is using the shorthand syntax starting with a dot,
- // make it a fully-qualified name by prepending it with the package name.
- if (name.startsWith('.')) {
- return manifest.'@package'.text() + name
- } else {
- return name
- }
- } catch (all) {
- return null
+ // If the activity name is using the shorthand syntax starting with a dot,
+ // make it a fully-qualified name by prepending it with the package name.
+ if (name.startsWith('.')) {
+ return manifest.'@package'.text() + name
+ } else {
+ return name
}
}
diff --git a/madb_test.go b/madb_test.go
index 44a2bf0..261c592 100644
--- a/madb_test.go
+++ b/madb_test.go
@@ -5,11 +5,23 @@
package main
import (
- "path"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"reflect"
"testing"
)
+func tempFilename(t *testing.T) string {
+ f, err := ioutil.TempFile("", "madb_test")
+ if err != nil {
+ t.Fatalf("could not open a temp file: %v", err)
+ }
+ f.Close()
+
+ return f.Name()
+}
+
func TestParseDevicesOutput(t *testing.T) {
var output string
@@ -193,14 +205,14 @@
projectDir string
want bool
}{
- {"projects/testProject", false},
- {"projects/testProject/android", false},
- {"projects/testProject/android/app", false},
- {"projects/testProject/flutter", true},
+ {"testMultiPlatform", false},
+ {"testMultiPlatform/android", false},
+ {"testMultiPlatform/android/app", false},
+ {"testMultiPlatform/flutter", true},
}
for i, testCase := range testCases {
- dir := path.Join("testdata", testCase.projectDir)
+ dir := filepath.Join("testdata", "projects", testCase.projectDir)
if got := isFlutterProject(dir); got != testCase.want {
t.Fatalf("unmatched results for testCases[%v]: got %v, want %v", i, got, testCase.want)
}
@@ -212,14 +224,14 @@
projectDir string
want bool
}{
- {"projects/testProject", false},
- {"projects/testProject/android", true},
- {"projects/testProject/android/app", true},
- {"projects/testProject/flutter", false},
+ {"testMultiPlatform", false},
+ {"testMultiPlatform/android", true},
+ {"testMultiPlatform/android/app", true},
+ {"testMultiPlatform/flutter", false},
}
for i, testCase := range testCases {
- dir := path.Join("testdata", testCase.projectDir)
+ dir := filepath.Join("testdata", "projects", testCase.projectDir)
if got := isGradleProject(dir); got != testCase.want {
t.Fatalf("unmatched results for testCases[%v]: got %v, want %v", i, got, testCase.want)
}
@@ -228,33 +240,104 @@
func TestExtractIdsFromGradle(t *testing.T) {
testCases := []struct {
- projectDir string
- want []string
+ key variantKey
+ want projectIds
}{
{
- "projects/testProject/android",
- []string{
- "io.v.testProjectId",
- "io.v.testProjectPackage.LauncherActivity",
- }},
+ variantKey{"testMultiPlatform/android", "", ""},
+ projectIds{"io.v.testProjectId", "io.v.testProjectPackage.LauncherActivity"},
+ },
{
- "projects/testProject/android/app",
- []string{
- "io.v.testProjectId",
- "io.v.testProjectPackage.LauncherActivity",
- }},
+ variantKey{"testMultiPlatform/android", "app", "debug"},
+ projectIds{"io.v.testProjectId", "io.v.testProjectPackage.LauncherActivity"},
+ },
+ {
+ variantKey{"testMultiPlatform/android/app", "", ""},
+ projectIds{"io.v.testProjectId", "io.v.testProjectPackage.LauncherActivity"},
+ },
+ {
+ variantKey{"testAndroidMultiFlavor", "", ""},
+ projectIds{"io.v.testProjectId.lite", "io.v.testProjectPackage.LauncherActivity"},
+ },
+ {
+ variantKey{"testAndroidMultiFlavor", "app", "liteDebug"},
+ projectIds{"io.v.testProjectId.lite.debug", "io.v.testProjectPackage.LauncherActivity"},
+ },
+ {
+ variantKey{"testAndroidMultiFlavor/app", "", "proRelease"},
+ projectIds{"io.v.testProjectId.pro", "io.v.testProjectPackage.LauncherActivity"},
+ },
}
for i, testCase := range testCases {
- dir := path.Join("testdata", testCase.projectDir)
-
- appID, activity, err := extractIdsFromGradle(dir)
+ testCase.key.Dir = filepath.Join("testdata", "projects", testCase.key.Dir)
+ got, err := extractIdsFromGradle(testCase.key)
if err != nil {
t.Fatalf("error occurred while extracting ids for testCases[%v]: %v", i, err)
}
- if got := []string{appID, activity}; !reflect.DeepEqual(got, testCase.want) {
+ if !reflect.DeepEqual(got, testCase.want) {
t.Fatalf("unmatched results for testCases[%v]: got %v, want %v", i, got, testCase.want)
}
}
}
+
+func TestGetProjectIds(t *testing.T) {
+ cacheFile := tempFilename(t)
+ defer os.Remove(cacheFile)
+
+ called := false
+
+ // See if it runs the extractor for the first time.
+ extractor := func(key variantKey) (projectIds, error) {
+ called = true
+ return projectIds{"testAppID", "Activity"}, nil
+ }
+
+ want := projectIds{"testAppID", "Activity"}
+ got, err := getProjectIds(extractor, variantKey{"testDir", "mod", "var"}, false, cacheFile)
+
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
+
+ if !called {
+ t.Fatalf("extractor was not called when expected to be called.")
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("unmatched results: got %v, want %v", got, want)
+ }
+
+ // The second run should not invoke the extractor.
+ called = false
+ got, err = getProjectIds(extractor, variantKey{"testDir", "mod", "var"}, false, cacheFile)
+
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
+
+ if called {
+ t.Fatalf("extracted was called when not expected.")
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("unmatched results: got %v, want %v", got, want)
+ }
+
+ // Run with clear cache flag.
+ called = false
+ got, err = getProjectIds(extractor, variantKey{"testDir", "mod", "var"}, true, cacheFile)
+
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
+
+ if !called {
+ t.Fatalf("extractor was not called when expected to be called.")
+ }
+
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("unmatched results: got %v, want %v", got, want)
+ }
+}
diff --git a/name.go b/name.go
index c8773af..bc5c7f0 100644
--- a/name.go
+++ b/name.go
@@ -185,8 +185,13 @@
return os.Remove(filename)
}
-func getDefaultNameFilePath() string {
- return filepath.Join(os.Getenv("HOME"), ".madb_names")
+func getDefaultNameFilePath() (string, error) {
+ configDir, err := getConfigDir()
+ if err != nil {
+ return "", err
+ }
+
+ return filepath.Join(configDir, "nicknames"), nil
}
func isValidDeviceSerial(serial string) bool {
@@ -262,5 +267,10 @@
// Run implements the cmdline.Runner interface by providing the default name file path
// as the third string argument of the underlying run function.
func (f runnerFuncWithFilepath) Run(env *cmdline.Env, args []string) error {
- return f(env, args, getDefaultNameFilePath())
+ p, err := getDefaultNameFilePath()
+ if err != nil {
+ return err
+ }
+
+ return f(env, args, p)
}
diff --git a/name_test.go b/name_test.go
index 21f002b..31480f2 100644
--- a/name_test.go
+++ b/name_test.go
@@ -6,7 +6,6 @@
import (
"fmt"
- "io/ioutil"
"os"
"reflect"
"testing"
@@ -17,16 +16,6 @@
b bool
}
-func tempFilename(t *testing.T) string {
- f, err := ioutil.TempFile("", "madb_test")
- if err != nil {
- t.Fatalf("could not open a temp file: %v", err)
- }
- f.Close()
-
- return f.Name()
-}
-
func TestMadbNameSet(t *testing.T) {
filename := tempFilename(t)
defer os.Remove(filename)
diff --git a/start.go b/start.go
index ca3282f..a112362 100644
--- a/start.go
+++ b/start.go
@@ -14,10 +14,21 @@
)
var (
+ forceStopFlag bool
+ clearCacheFlag bool
+ moduleFlag string
+ variantFlag string
+
wd string // working directory
)
func init() {
+ cmdMadbStart.Flags.BoolVar(&forceStopFlag, "force-stop", true, `Force stop the target app before starting the activity.`)
+ cmdMadbStart.Flags.BoolVar(&clearCacheFlag, "clear-cache", false, `Clear the cache and re-extract the application ID and the main activity name. Only takes effect when no arguments are provided.`)
+ cmdMadbStart.Flags.StringVar(&moduleFlag, "module", "", `Specify which application module to use, when the current directory is the top level Gradle project containing multiple sub-modules. When not specified, the first available application module is used. Only takes effect when no arguments are provided.`)
+ cmdMadbStart.Flags.StringVar(&variantFlag, "variant", "", `Specify which build variant to use. When not specified, the first available build variant is used. Only takes effect when no arguments are provided.`)
+
+ // Store the current working directory.
var err error
wd, err = os.Getwd()
if err != nil {
@@ -33,13 +44,29 @@
Launches your app on all devices.
`,
- ArgsName: "<application_id> <activity_name>",
+ ArgsName: "[<application_id> <activity_name>]",
ArgsLong: `
<application_id> is usually the package name where the activities are defined.
(See: http://tools.android.com/tech-docs/new-build-system/applicationid-vs-packagename)
<activity_name> is the Java class name for the activity you want to launch.
-If the package name of the activity is different from the application ID, the activity name must be a fully-qualified name (e.g., com.yourcompany.yourapp.MainActivity).
+If the package name of the activity is different from the application ID, the activity name must be
+a fully-qualified name (e.g., com.yourcompany.yourapp.MainActivity).
+
+If either <application_id> or <activity_name> is provided, the other must be provided as well.
+
+
+If no arguments are specified, madb automatically determines which app to launch, based on the build
+scripts found in the current working directory.
+
+1) If the working directory contains a Flutter project (i.e., has "flutter.yaml"), this command will
+run "flutter start --android-device-id=<device serial>" for all the specified devices.
+
+2) If the working directory contains a Gradle Android project (i.e., has "build.gradle"), this
+command will run a small Gradle script to extract the application ID and the main activity name.
+In this case, the extracted IDs are cached, so that "madb start" can be repeated without even
+running the Gradle script again. The IDs can be re-extracted by clearing the cache by providing
+"-clear-cache" flag.
`,
}
@@ -54,16 +81,19 @@
}
// Try to extract the application ID and the main activity name from the Gradle scripts.
- // TODO(youngseokyoon): cache the ids, since the ids are not supposed to be changed very often.
if isGradleProject(wd) {
- fmt.Println("Running Gradle to extract the application ID and the main activity name...")
-
- appID, activity, err := extractIdsFromGradle(wd)
+ cacheFile, err := getDefaultCacheFilePath()
if err != nil {
return nil, err
}
- args = []string{appID, activity}
+ key := variantKey{wd, moduleFlag, variantFlag}
+ ids, err := getProjectIds(extractIdsFromGradle, key, clearCacheFlag, cacheFile)
+ if err != nil {
+ return nil, err
+ }
+
+ args = []string{ids.AppID, ids.Activity}
}
return args, nil
@@ -85,9 +115,12 @@
activity = "." + activity
}
- // TODO(youngseokyoon): add a flag for not stopping the activity when it is currently running.
// More details on the "adb shell am" command can be found at: http://developer.android.com/tools/help/shell.html#am
- cmdArgs := []string{"-s", d.Serial, "shell", "am", "start", "-S", "-n", appID + "/" + activity}
+ cmdArgs := []string{"-s", d.Serial, "shell", "am", "start"}
+ if forceStopFlag {
+ cmdArgs = append(cmdArgs, "-S")
+ }
+ cmdArgs = append(cmdArgs, "-n", appID+"/"+activity)
cmd := sh.Cmd("adb", cmdArgs...)
return runGoshCommandForDevice(cmd, d)
}
diff --git a/testdata/projects/testAndroidMultiFlavor/app/build.gradle b/testdata/projects/testAndroidMultiFlavor/app/build.gradle
new file mode 100644
index 0000000..ba9bb1e
--- /dev/null
+++ b/testdata/projects/testAndroidMultiFlavor/app/build.gradle
@@ -0,0 +1,53 @@
+buildscript {
+ repositories {
+ jcenter()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.0'
+ classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.+'
+ }
+}
+
+apply plugin: 'android-sdk-manager'
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 23
+ buildToolsVersion "23.0.1"
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ defaultConfig {
+ applicationId "io.v.testProjectId"
+ minSdkVersion 23
+ targetSdkVersion 23
+ versionCode 1
+ versionName "1.0"
+ }
+ productFlavors {
+ lite {
+ applicationId "io.v.testProjectId.lite"
+ }
+ pro {
+ applicationId "io.v.testProjectId.pro"
+ }
+ }
+ buildTypes {
+ debug {
+ applicationIdSuffix ".debug"
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+}
+
diff --git a/testdata/projects/testProject/android/app/src/main/AndroidManifest.xml b/testdata/projects/testAndroidMultiFlavor/app/src/main/AndroidManifest.xml
similarity index 100%
copy from testdata/projects/testProject/android/app/src/main/AndroidManifest.xml
copy to testdata/projects/testAndroidMultiFlavor/app/src/main/AndroidManifest.xml
diff --git a/testdata/projects/testProject/android/build.gradle b/testdata/projects/testAndroidMultiFlavor/build.gradle
similarity index 100%
copy from testdata/projects/testProject/android/build.gradle
copy to testdata/projects/testAndroidMultiFlavor/build.gradle
diff --git a/testdata/projects/testProject/android/gradle.properties b/testdata/projects/testAndroidMultiFlavor/gradle.properties
similarity index 100%
copy from testdata/projects/testProject/android/gradle.properties
copy to testdata/projects/testAndroidMultiFlavor/gradle.properties
diff --git a/testdata/projects/testProject/android/gradle/wrapper/gradle-wrapper.jar b/testdata/projects/testAndroidMultiFlavor/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
copy from testdata/projects/testProject/android/gradle/wrapper/gradle-wrapper.jar
copy to testdata/projects/testAndroidMultiFlavor/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/testdata/projects/testProject/android/gradle/wrapper/gradle-wrapper.properties b/testdata/projects/testAndroidMultiFlavor/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
copy from testdata/projects/testProject/android/gradle/wrapper/gradle-wrapper.properties
copy to testdata/projects/testAndroidMultiFlavor/gradle/wrapper/gradle-wrapper.properties
diff --git a/testdata/projects/testProject/android/gradlew b/testdata/projects/testAndroidMultiFlavor/gradlew
similarity index 100%
copy from testdata/projects/testProject/android/gradlew
copy to testdata/projects/testAndroidMultiFlavor/gradlew
diff --git a/testdata/projects/testProject/android/settings.gradle b/testdata/projects/testAndroidMultiFlavor/settings.gradle
similarity index 100%
copy from testdata/projects/testProject/android/settings.gradle
copy to testdata/projects/testAndroidMultiFlavor/settings.gradle
diff --git a/testdata/projects/testProject/android/app/build.gradle b/testdata/projects/testMultiPlatform/android/app/build.gradle
similarity index 100%
rename from testdata/projects/testProject/android/app/build.gradle
rename to testdata/projects/testMultiPlatform/android/app/build.gradle
diff --git a/testdata/projects/testProject/android/app/src/main/AndroidManifest.xml b/testdata/projects/testMultiPlatform/android/app/src/main/AndroidManifest.xml
similarity index 100%
rename from testdata/projects/testProject/android/app/src/main/AndroidManifest.xml
rename to testdata/projects/testMultiPlatform/android/app/src/main/AndroidManifest.xml
diff --git a/testdata/projects/testProject/android/build.gradle b/testdata/projects/testMultiPlatform/android/build.gradle
similarity index 100%
rename from testdata/projects/testProject/android/build.gradle
rename to testdata/projects/testMultiPlatform/android/build.gradle
diff --git a/testdata/projects/testProject/android/gradle.properties b/testdata/projects/testMultiPlatform/android/gradle.properties
similarity index 100%
rename from testdata/projects/testProject/android/gradle.properties
rename to testdata/projects/testMultiPlatform/android/gradle.properties
diff --git a/testdata/projects/testProject/android/gradle/wrapper/gradle-wrapper.jar b/testdata/projects/testMultiPlatform/android/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
rename from testdata/projects/testProject/android/gradle/wrapper/gradle-wrapper.jar
rename to testdata/projects/testMultiPlatform/android/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/testdata/projects/testProject/android/gradle/wrapper/gradle-wrapper.properties b/testdata/projects/testMultiPlatform/android/gradle/wrapper/gradle-wrapper.properties
similarity index 100%
rename from testdata/projects/testProject/android/gradle/wrapper/gradle-wrapper.properties
rename to testdata/projects/testMultiPlatform/android/gradle/wrapper/gradle-wrapper.properties
diff --git a/testdata/projects/testProject/android/gradlew b/testdata/projects/testMultiPlatform/android/gradlew
similarity index 100%
rename from testdata/projects/testProject/android/gradlew
rename to testdata/projects/testMultiPlatform/android/gradlew
diff --git a/testdata/projects/testProject/android/settings.gradle b/testdata/projects/testMultiPlatform/android/settings.gradle
similarity index 100%
rename from testdata/projects/testProject/android/settings.gradle
rename to testdata/projects/testMultiPlatform/android/settings.gradle
diff --git a/testdata/projects/testProject/flutter/flutter.yaml b/testdata/projects/testMultiPlatform/flutter/flutter.yaml
similarity index 100%
rename from testdata/projects/testProject/flutter/flutter.yaml
rename to testdata/projects/testMultiPlatform/flutter/flutter.yaml