1

For reasons particular to my app, I'd like to use two different versions of a Google Play Services library in two different productFlavors. But gradle gives me the familiar error message:

Please fix the version conflict either by updating the version of the google-services plugin (information about the latest version is available at https://bintray.com/android/android-tools/com.google.gms.google-services/) or updating the version of com.google.android.gms to 10.2.4.

Normally I'd fix this by using consistent versions of GPS libraries. But, in this case I thought the inconsistency would be ok because I'm compiling the app into two separate flavors. It's not quite working:

app build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion '25.0.2'

    defaultConfig {
        applicationId "com.albertcbraun.googleplayservicesversionconflicttestcase"
        minSdkVersion 16
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    productFlavors {
        flavor1 {
            applicationId 'com.albertcbraun.flavor1'
        }

        flavor2 {
            applicationId 'com.albertcbraun.flavor2'
        }
    }
}


dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'

    // these are the problematic lines. GPS versions differ: 
    flavor1Compile 'com.google.android.gms:play-services-identity:10.2.4'
    flavor2Compile 'com.google.android.gms:play-services-identity:9.6.1'
}

apply plugin: 'com.google.gms.google-services'

project build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.1'
        classpath 'com.google.gms:google-services:3.0.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Gradle Version: 3.3

Android Studio Version: 2.3.1

albert c braun
  • 2,650
  • 1
  • 22
  • 32
  • https://stackoverflow.com/questions/18196974/how-to-define-different-dependencies-for-different-product-flavors – ashkhn May 02 '17 at 04:26

2 Answers2

0

No gradle doesn't support multiple version of the same library. It will choose the newest, Gradle by default uses the newest of conflicting versions. However, you can change this behavior. Use this method to configure the resolution to fail eagerly on any version conflict, e.g. multiple different versions of the same dependency (group and name are equal) in the same Configuration.

Sources from here https://gradle.org/docs/current/dsl/org.gradle.api.artifacts.ResolutionStrategy.html

ZeroOne
  • 8,996
  • 4
  • 27
  • 45
  • thanks for your response and the pointer to that documentation. it's interesting. but, one thing i've noticed is that the android gradle plugin does allow me to compile two different versions of the same library in two different flavors, if the library is not a GPS library. (e.g. com.android.support:appcompat-v7:25.3.0 and com.android.support:appcompat-v7:25.3.1) – albert c braun May 02 '17 at 15:51
0

======== REVISED 04/05/2017 ========

This is experimental, but, FWIW, I was able to hack in "flavor awareness" to version 3.0.0 of GoogleServicesTask.java and GoogleServicesPlugin.groovy (which make up the GoogleServicesPlugin for gradle).

The original plugin infers the GPS library version by examining the 'compile' statements in your build.gradle (in a method called findTargetVersion). But I changed that. With this hack, you pre-specify these versions per flavor in the extension properties.

This approach is neither well tested nor production-ready, but it is able to compile two different versions of the GPS libraries with two different product flavors. Also note: Android Studio complains a little about the fact that you have two different versions (red underlining), but AS should still let you select either of your flavors in buildvariants and actually carry out a build. (At least, it did for me.)

First add these two extension values (or whatever versions you want to use) in the same build.gradle, somewhere sensible:

ext.flavor1GPSVersion = "10.2.1"
ext.flavor2GPSVersion = "10.2.4"

Second, comment out or remove this 'apply' line from your app module's build.gradle:

apply plugin: GoogleServicesPlugin

Finally, directly paste the following modified versions of the GoogleServicesTask.java and GoogleServicesPlugin.groovy into the bottom of that build.gradle file (and remember to include the new 'apply plugin' line at the bottom):

// ************************************************************//
// ********** Multi Flavor Google Services Plugin *************//
// ************************************************************//

import org.gradle.api.tasks.Optional;

import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;


class MultiFlavorGoogleServicesPlugin implements Plugin<Project> {

    public final static String JSON_FILE_NAME = 'google-services.json'

    public final static String MODULE_GROUP = "com.google.android.gms"
    public final static String MODULE_GROUP_FIREBASE = "com.google.firebase"
    public final static String MODULE_CORE = "firebase-core"
    public final static String MINIMUM_VERSION = "9.0.0"

    private static final String TAG = "GoogleServicesPlugin";

    @Override
    void apply(Project project) {
        if (project.plugins.hasPlugin("android") ||
                project.plugins.hasPlugin("com.android.application")) {
            // this is a bit fragile but since this is internal usage this is ok
            // (another plugin could declare itself to be 'android')
            for (def flavor : project.android.productFlavors) {
                addDependency(project, flavor.name)
            }
            setupPlugin(project, false)
            return
        }
        if (project.plugins.hasPlugin("android-library") ||
                project.plugins.hasPlugin("com.android.library")) {
            // this is a bit fragile but since this is internal usage this is ok
            // (another plugin could declare itself to be 'android-library')
            for (def flavor : project.android.productFlavors) {
                addDependency(project, flavor.name)
            }
            setupPlugin(project, true)
            return
        }
        // If the google-service plugin is applied before any android plugin.
        // We should warn that google service plugin should be applied at
        // the bottom of build file.
        showWarningForPluginLocation(project)

        // Setup google-services plugin after android plugin is applied.
        project.plugins.withId("android", {
            setupPlugin(project, false)
        })
        project.plugins.withId("android-library", {
            setupPlugin(project, true)
        })

        // Add dependencies after the build file is evaluate and hopefully it
        // can be execute before android plugin process the dependencies.
        for (def flavor : project.android.productFlavors) {
            project.afterEvaluate({
                addDependency(project, flavor.name)
            })
        }
    }

    private static void showWarningForPluginLocation(Project project) {
        project.getLogger().warn(
                "please apply google-services plugin at the bottom of the build file.")
    }

    private static boolean checkMinimumVersion(Project project, String flavorName) {
        String[] subTargetVersions = findTargetVersion(project, flavorName).split("\\.")    //targetVersion.split("\\.")
        String[] subMinimumVersions = MINIMUM_VERSION.split("\\.")
        for (int i = 0; i < subTargetVersions.length && i < subMinimumVersions.length; i++) {
            Integer subTargetVersion = Integer.valueOf(subTargetVersions[i])
            Integer subMinimumVersion = Integer.valueOf(subMinimumVersions[i])
            if (subTargetVersion > subMinimumVersion) {
                return true;
            } else if (subTargetVersion < subMinimumVersion) {
                return false;
            }
        }
        return subTargetVersions.length >= subMinimumVersions.length;
    }

    private void addDependency(Project project, String flavorName) {
        //targetVersion = findTargetVersion(project).split("-")[0]
        if (checkMinimumVersion(project, flavorName)) {
            // If the target version is not lower than the minimum version
            project.dependencies.add('compile', MODULE_GROUP_FIREBASE + ':' + MODULE_CORE + ':' + findTargetVersion(project, flavorName).split("-")[0])
        } else {
            throw new GradleException("Version: " + targetVersion + " is lower than the minimum version (" +
                    MINIMUM_VERSION + ") required for google-services plugin.")
        }
    }

    private static String findTargetVersion(Project project, String flavorName) {
        return project.ext[flavorName + "GPSVersion"];
    }

    private void setupPlugin(Project project, boolean isLibrary) {
        if (isLibrary) {
            project.android.libraryVariants.all { variant ->
                handleVariant(project, variant)
            }
        } else {
            project.android.applicationVariants.all { variant ->
                handleVariant(project, variant)
            }
        }
    }

    private static void handleVariant(Project project,
                                      def variant) {

        File quickstartFile = null

        String variantName = "$variant.dirName";
        String[] variantTokens = variantName.split('/')

        List<String> fileLocation = new ArrayList<>()

        FlavorAwareGoogleServicesTask task = project.tasks
                .create("process${variant.name.capitalize()}GoogleServices",
                FlavorAwareGoogleServicesTask)

        if (variantTokens.length == 2) {
            // If flavor and buildType are found.
            String flavorName = variantTokens[0]
            String buildType = variantTokens[1]
            fileLocation.add('src/' + flavorName + '/' + buildType)
            fileLocation.add('src/' + buildType + '/' + flavorName)
            fileLocation.add('src/' + flavorName)
            fileLocation.add('src/' + buildType)
            task.moduleVersion = findTargetVersion(project, flavorName)
            task.flavorName = flavorName;
        } else if (variantTokens.length == 1) {
            // If only buildType is found.
            fileLocation.add('src/' + variantTokens[0])
        }

        String searchedLocation = System.lineSeparator()
        for (String location : fileLocation) {
            File jsonFile = project.file(location + '/' + JSON_FILE_NAME)
            searchedLocation = searchedLocation + jsonFile.getPath() + System.lineSeparator()
            if (jsonFile.isFile()) {
                quickstartFile = jsonFile
                break
            }
        }

        if (quickstartFile == null) {
            quickstartFile = project.file(JSON_FILE_NAME)
            searchedLocation = searchedLocation + quickstartFile.getPath()
        }

        File outputDir =
                project.file("$project.buildDir/generated/res/google-services/$variant.dirName")

        task.quickstartFile = quickstartFile
        task.intermediateDir = outputDir
        task.packageName = variant.applicationId
        task.moduleGroup = MODULE_GROUP
        // Use the target version for the task.
        //task.moduleVersion = targetVersion;
        variant.registerResGeneratingTask(task, outputDir)
        task.searchedLocation = searchedLocation
    }

}


/**
 * Helper task for plugin
 * */
public class FlavorAwareGoogleServicesTask extends DefaultTask {

    private static final String STATUS_DISABLED = "1";
    private static final String STATUS_ENABLED = "2";

    private static final String OAUTH_CLIENT_TYPE_WEB = "3";

    /**
     * The input is not technically optional but we want to control the error message.
     * Without @Optional, Gradle will complain itself the file is missing.
     */
    @InputFile @Optional
    public File quickstartFile;

    @OutputDirectory
    public File intermediateDir;

    @Input
    public String packageName;

    @Input
    public String moduleGroup;

    @Input
    public String moduleVersion;

    @Input
    public String searchedLocation;

    @Input
    public String flavorName;

    @TaskAction
    public void action() throws IOException {
        checkVersionConflict();
        if (!quickstartFile.isFile()) {
            throw new GradleException(String.format("File %s is missing. " +
                    "The Google Services Plugin cannot function without it. %n Searched Location: %s",
                    quickstartFile.getName(), searchedLocation));
        }

        getProject().getLogger().warn("Parsing json file: " + quickstartFile.getPath());

        // delete content of outputdir.
        deleteFolder(intermediateDir);
        if (!intermediateDir.mkdirs()) {
            throw new GradleException("Failed to create folder: " + intermediateDir);
        }

        JsonElement root = new JsonParser().parse(Files.newReader(quickstartFile, Charsets.UTF_8));

        if (!root.isJsonObject()) {
            throw new GradleException("Malformed root json");
        }

        JsonObject rootObject = root.getAsJsonObject();

        Map<String, String> resValues = new TreeMap<String, String>();
        Map<String, Map<String, String>> resAttributes = new TreeMap<String, Map<String, String>>();

        handleProjectNumberAndProjectId(rootObject, resValues);
        handleFirebaseUrl(rootObject, resValues);

        JsonObject clientObject = getClientForPackageName(rootObject);

        if (clientObject != null) {
            handleAnalytics(clientObject, resValues);
            handleMapsService(clientObject, resValues);
            handleGoogleApiKey(clientObject, resValues);
            handleGoogleAppId(clientObject, resValues);
            handleWebClientId(clientObject, resValues);
        } else {
            throw new GradleException("No matching client found for package name '" + packageName + "'");
        }

        // write the values file.
        File values = new File(intermediateDir, "values");
        if (!values.exists() && !values.mkdirs()) {
            throw new GradleException("Failed to create folder: " + values);
        }

        Files.write(getValuesContent(resValues, resAttributes), new File(values, "values.xml"), Charsets.UTF_8);
    }

    /**
     * Check if there is any conflict between Play-Services Version
     */
    private void checkVersionConflict() {
        Project project = getProject();
        ConfigurationContainer configurations = project.getConfigurations();
        if (configurations == null) {
            return;
        }
        boolean hasConflict = false;
        for (Configuration configuration : configurations) {
            if (configuration == null) {
                continue;
            }
            if (configuration.name.startsWith(flavorName + "Compile")) {
                DependencySet dependencies = configuration.getDependencies();
                if (dependencies == null) {
                    continue;
                }

                for (Dependency dependency : dependencies) {
                    if (dependency == null || dependency.getGroup() == null || dependency.getVersion() == null) {
                        continue;
                    }
                    println("checkVersionConflict for flavor:" + flavorName +
                            " comparing moduleGroup:" + moduleGroup + " to " + dependency.getGroup() +
                            " moduleVersion:" + moduleVersion + " to " + dependency.getVersion());
                    if (dependency.getGroup().equals(moduleGroup)
                            && !dependency.getVersion().equals(moduleVersion)) {
                        hasConflict = true;
                        project.getLogger().warn("Found " + dependency.getGroup() + ":" +
                                dependency.getName() + ":" + dependency.getVersion() + ", but version " +
                                moduleVersion + " is needed for the google-services plugin.");
                    }
                }
            }

        }
        if (hasConflict) {
            throw new GradleException("Please fix the version conflict either by updating the version " +
                    "of the google-services plugin (information about the latest version is available at " +
                    "https://bintray.com/android/android-tools/com.google.gms.google-services/) or updating " +
                    "the version of " + moduleGroup + " to " + moduleVersion + ".");
        }
    }

    private void handleFirebaseUrl(JsonObject rootObject, Map<String, String> resValues)
            throws IOException {
        JsonObject projectInfo = rootObject.getAsJsonObject("project_info");
        if (projectInfo == null) {
            throw new GradleException("Missing project_info object");
        }

        JsonPrimitive firebaseUrl = projectInfo.getAsJsonPrimitive("firebase_url");
        if (firebaseUrl != null) {
            resValues.put("firebase_database_url", firebaseUrl.getAsString());
        }
    }

    /**
     * Handle project_info/project_number for @string/gcm_defaultSenderId, and fill the res map with the read value.
     * @param rootObject the root Json object.
     * @throws IOException
     */
    private void handleProjectNumberAndProjectId(JsonObject rootObject, Map<String, String> resValues)
            throws IOException {
        JsonObject projectInfo = rootObject.getAsJsonObject("project_info");
        if (projectInfo == null) {
            throw new GradleException("Missing project_info object");
        }

        JsonPrimitive projectNumber = projectInfo.getAsJsonPrimitive("project_number");
        if (projectNumber == null) {
            throw new GradleException("Missing project_info/project_number object");
        }

        resValues.put("gcm_defaultSenderId", projectNumber.getAsString());

        JsonPrimitive bucketName = projectInfo.getAsJsonPrimitive("storage_bucket");
        if (bucketName != null) {
            resValues.put("google_storage_bucket", bucketName.getAsString());
        }
    }

    private void handleWebClientId(JsonObject clientObject, Map<String, String> resValues) {
        JsonArray array = clientObject.getAsJsonArray("oauth_client");
        if (array != null) {
            final int count = array.size();
            for (int i = 0 ; i < count ; i++) {
                JsonElement oauthClientElement = array.get(i);
                if (oauthClientElement == null || !oauthClientElement.isJsonObject()) {
                    continue;
                }
                JsonObject oauthClientObject = oauthClientElement.getAsJsonObject();
                JsonPrimitive clientType = oauthClientObject.getAsJsonPrimitive("client_type");
                if (clientType == null) {
                    continue;
                }
                String clientTypeStr = clientType.getAsString();
                if (!OAUTH_CLIENT_TYPE_WEB.equals(clientTypeStr)) {
                    continue;
                }
                JsonPrimitive clientId = oauthClientObject.getAsJsonPrimitive("client_id");
                if (clientId == null) {
                    continue;
                }
                resValues.put("default_web_client_id", clientId.getAsString());
                return;
            }
        }
    }

    /**
     * Handle a client object for analytics (@xml/global_tracker)
     * @param clientObject the client Json object.
     * @throws IOException
     */
    private void handleAnalytics(JsonObject clientObject, Map<String, String> resValues)
            throws IOException {
        JsonObject analyticsService = getServiceByName(clientObject, "analytics_service");
        if (analyticsService == null) return;

        JsonObject analyticsProp = analyticsService.getAsJsonObject("analytics_property");
        if (analyticsProp == null) return;

        JsonPrimitive trackingId = analyticsProp.getAsJsonPrimitive("tracking_id");
        if (trackingId == null) return;

        resValues.put("ga_trackingId", trackingId.getAsString());

        File xml = new File(intermediateDir, "xml");
        if (!xml.exists() && !xml.mkdirs()) {
            throw new GradleException("Failed to create folder: " + xml);
        }

        Files.write(getGlobalTrackerContent(
                trackingId.getAsString()),
                new File(xml, "global_tracker.xml"),
                Charsets.UTF_8);
    }

    /**
     * Handle a client object for maps (@string/google_maps_key).
     * @param clientObject the client Json object.
     * @throws IOException
     */
    private void handleMapsService(JsonObject clientObject, Map<String, String> resValues)
            throws IOException {
        JsonObject mapsService = getServiceByName(clientObject, "maps_service");
        if (mapsService == null) return;

        String apiKey = getAndroidApiKey(clientObject);
        if (apiKey != null) {
            resValues.put("google_maps_key", apiKey);
            return;
        }
        throw new GradleException("Missing api_key/current_key object");
    }

    private void handleGoogleApiKey(JsonObject clientObject, Map<String, String> resValues) {
        String apiKey = getAndroidApiKey(clientObject);
        if (apiKey != null) {
            resValues.put("google_api_key", apiKey);
            // TODO: remove this once SDK starts to use google_api_key.
            resValues.put("google_crash_reporting_api_key", apiKey);
            return;
        }

        // if google_crash_reporting_api_key is missing.
        // throw new GradleException("Missing api_key/current_key object");
        throw new GradleException("Missing api_key/current_key object");
    }

    private String getAndroidApiKey(JsonObject clientObject) {
        JsonArray array = clientObject.getAsJsonArray("api_key");
        if (array != null) {
            final int count = array.size();
            for (int i = 0 ; i < count ; i++) {
                JsonElement apiKeyElement = array.get(i);
                if (apiKeyElement == null || !apiKeyElement.isJsonObject()) {
                    continue;
                }
                JsonObject apiKeyObject = apiKeyElement.getAsJsonObject();
                JsonPrimitive currentKey = apiKeyObject.getAsJsonPrimitive("current_key");
                if (currentKey == null) {
                    continue;
                }
                return currentKey.getAsString();
            }
        }
        return null;
    }


    /**
     * find an item in the "client" array that match the package name of the app
     * @param jsonObject the root json object.
     * @return a JsonObject representing the client entry or null if no match is found.
     */
    private JsonObject getClientForPackageName(JsonObject jsonObject) {
        JsonArray array = jsonObject.getAsJsonArray("client");
        if (array != null) {
            final int count = array.size();
            for (int i = 0 ; i < count ; i++) {
                JsonElement clientElement = array.get(i);
                if (clientElement == null || !clientElement.isJsonObject()) {
                    continue;
                }

                JsonObject clientObject = clientElement.getAsJsonObject();

                JsonObject clientInfo = clientObject.getAsJsonObject("client_info");
                if (clientInfo == null) continue;

                JsonObject androidClientInfo = clientInfo.getAsJsonObject("android_client_info");
                if (androidClientInfo == null) continue;

                JsonPrimitive clientPackageName = androidClientInfo.getAsJsonPrimitive("package_name");
                if (clientPackageName == null) continue;

                if (packageName.equals(clientPackageName.getAsString())) {
                    return clientObject;
                }
            }
        }

        return null;
    }

    /**
     * Handle a client object for Google App Id.
     */
    private void handleGoogleAppId(JsonObject clientObject, Map<String, String> resValues)
            throws IOException {
        JsonObject clientInfo = clientObject.getAsJsonObject("client_info");
        if (clientInfo == null) {
            // Should not happen
            throw new GradleException("Client does not have client info");
        }

        JsonPrimitive googleAppId = clientInfo.getAsJsonPrimitive("mobilesdk_app_id");
        if (googleAppId == null) return;

        String googleAppIdStr = googleAppId.getAsString();
        if (Strings.isNullOrEmpty(googleAppIdStr)) return;

        resValues.put("google_app_id", googleAppIdStr);
    }

    /**
     * Finds a service by name in the client object. Returns null if the service is not found
     * or if the service is disabled.
     *
     * @param clientObject the json object that represents the client.
     * @param serviceName the service name
     * @return the service if found.
     */
    private JsonObject getServiceByName(JsonObject clientObject, String serviceName) {
        JsonObject services = clientObject.getAsJsonObject("services");
        if (services == null) return null;

        JsonObject service = services.getAsJsonObject(serviceName);
        if (service == null) return null;

        JsonPrimitive status = service.getAsJsonPrimitive("status");
        if (status == null) return null;

        String statusStr = status.getAsString();

        if (STATUS_DISABLED.equals(statusStr)) return null;
        if (!STATUS_ENABLED.equals(statusStr)) {
            getLogger().warn(String.format("Status with value '%1$s' for service '%2$s' is unknown",
                    statusStr,
                    serviceName));
            return null;
        }

        return service;
    }

    private static String getGlobalTrackerContent(String ga_trackingId) {
        return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n" +
                "    <string name=\"ga_trackingId\" translatable=\"false\">" + ga_trackingId + "</string>\n" +
                "</resources>\n";
    }

    private static String getValuesContent(Map<String, String> values,
                                           Map<String, Map<String, String>> attributes) {
        StringBuilder sb = new StringBuilder(256);

        sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
                "<resources>\n");

        for (Map.Entry<String, String> entry : values.entrySet()) {
            String name = entry.getKey();
            sb.append("    <string name=\"").append(name).append("\" translatable=\"false\"");
            if (attributes.containsKey(name)) {
                for (Map.Entry<String, String> attr : attributes.get(name).entrySet()) {
                    sb.append(" ").append(attr.getKey()).append("=\"")
                            .append(attr.getValue()).append("\"");
                }
            }
            sb.append(">").append(entry.getValue()).append("</string>\n");
        }

        sb.append("</resources>\n");

        return sb.toString();
    }

    private static void deleteFolder(final File folder) {
        if (!folder.exists()) {
            return;
        }
        File[] files = folder.listFiles();
        if (files != null) {
            for (final File file : files) {
                if (file.isDirectory()) {
                    deleteFolder(file);
                } else {
                    if (!file.delete()) {
                        throw new GradleException("Failed to delete: " + file);
                    }
                }
            }
        }
        if (!folder.delete()) {
            throw new GradleException("Failed to delete: " + folder);
        }
    }
}


apply plugin: MultiFlavorGoogleServicesPlugin

======== ORIGINAL 04/04/2017 =======

Looking into this further, I found that the google-services plugin is the source of the error message and the constraint that only one version of a GPS library dependency can be used. (If you comment out apply plugin: 'com.google.gms.google-services' in the build.gradle above, then the error message doesn't happen. But, you really need that plugin, so commenting it out is not a solution.)

In order to do this, you would need to create a modified version of the google-services plugin (which is applied at the bottom of the app build.gradle above).

The google-services plugin consists of two files: GoogleServicesTask.java and GoogleServicesPlugin.groovy. (These can be found deep beneath the 'caches' subdir of gradle home).

It appears that GoogleServicesTask.java insists on just using the first version of the GPS library it finds (in the method findTargetVersion).

(edited out to save characters)

albert c braun
  • 2,650
  • 1
  • 22
  • 32