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

[TIMOB-25353] Android: Allow Titanium modules to process module dependencies #9481

Merged
merged 6 commits into from Nov 9, 2017

Conversation

garymathews
Copy link
Contributor

@garymathews garymathews commented Sep 26, 2017

appc-aar-tools#16 NEEDS TO BE MERGED FIRST!

In an attempt to minimise conflicting Google Play Services library versions I have implemented the ability for Titanium Android native modules to have dependencies on other Titanium Android native modules.

This is to allow dependency on the ti.playservices module for modules such as ti.map and ti.admob for Google Play Services.

This will prevent the need to update each modules Play Services library and negate the need for module developers to include their own Play Services library which could conflict with existing modules.

TEST CASE
  • Acquire ti.map-3.3.2 pre-release
  • Add ti.playservices and ti.map into your tiapp.xml
<android xmlns:android="http://schemas.android.com/apk/res/android">
  <manifest>  
    <application>
      <meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="<YOUR API KEY>"/>
    </application>
  </manifest>
</android>
<modules>
    <module platform="android" version="3.3.2">ti.map</module>
    <module platform="android" version="11.0.40">ti.playservices</module>
</modules>
var win = Ti.UI.createWindow(),
    Map = require('ti.map'),
    a = Map.createAnnotation({
        title: 'Axway Appcelerator',
        latitude: 37.38935, longitude: -121.980,
        pincolor: Map.ANNOTATION_GREEN
    }),
    map = Map.createView({
        userLocation: true,
        mapType: Map.NORMAL_TYPE,
        animate: true,
        annotations: [a],
        region: {
            latitude: 37.3680, longitude: -121.9145,
            latitudeDelta: 0.1, longitudeDelta: 0.1
        }
    });

map.addAnnotation(a);

win.add(map);
win.open();
  • Map should display with an annotation

JIRA Ticket

@garymathews garymathews added this to the 7.0.0 milestone Sep 26, 2017
@garymathews garymathews changed the title [TIMOB-24722] Android: Add Play Services 11.0.4 module [TIMOB-25353] Android: Add Play Services 11.0.4 module Sep 26, 2017
@build
Copy link
Contributor

build commented Sep 26, 2017

Warnings
⚠️

🔒 Changes were made to package.json, but not to package-lock.json - Perhaps you need to run npm install?

Generated by 🚫 dangerJS

@garymathews
Copy link
Contributor Author

After discussion with @jquick-axway it's probably best this exists as an external module.

@benbahrenburg
Copy link
Contributor

@garymathews if implemented as an external module will this still remove the conflicts that module developers have? Is there anyway we can have a standard which we (module developers) build against?

@jquick-axway
Copy link
Contributor

@benbahrenburg, the idea is to wrap the Google Play Services main JAR in its own module and to have other modules which depend on it (ex: ti.map) remove the Google Play Services JAR file from their "lib" directory. It'll be our responsibility to keep the Google Play Services module up-to-date. So, this will only resolve dependency conflicts if other modules "play ball", remove their Google Play Services JAR, and instead document that they require this new module as a dependency.

For example, if an app developer wanted to use the "ti.map" module, the developer would have to add the "ti.map" module and the Google Play Services module in order for it to work. However, doing so with an existing module would be a breaking change, so, we'll look into providing a sensible warning message when attempting to a build while a module dependency is missing. May involve having adding a new feature to "timodule.xml" to reference another module as a dependency.

As an added benefit to making Google Play Services its own module, we'll likely expose the APIs needed to ensure that it's installed and enabled on the device as documented by Google below. My impression is that most modules are not doing this right now, which is why we're holding on to an old version of this library. So, exposing these APIs is needed since the Google Play Services library doesn't contain implementation for most of its functionality and instead does inter-process communications with the Google Play app to request it to perform the needed operations.

What do you think?

@garymathews garymathews force-pushed the TIMOB-24722 branch 3 times, most recently from 532a4f2 to 0a46faa Compare September 27, 2017 23:14
@benbahrenburg
Copy link
Contributor

@jquick-axway sounds like a good plan. I had tried to keep the different versions of Play straight using this repo https://github.com/benbahrenburg/Ti-Google-Play-Services but it never really worked out.

Having one place that everyone could get the latest version to compile against I think would be a welcome addition.

@garymathews garymathews changed the title [TIMOB-25353] Android: Add Play Services 11.0.4 module [TIMOB-25353] Android: Allow Titanium modules to process module dependencies Sep 28, 2017
@garymathews
Copy link
Contributor Author

@janvennemann Updated PR

Copy link
Contributor

@jquick-axway jquick-axway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@janvennemann, do you have any feedback you want to give?

Copy link
Contributor

@janvennemann janvennemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few minor notes regarding code style. Apart from that it looks good to me. Nice feature!


// process module dependencies
this.modules = this.timodule && !Array.isArray(this.timodule.modules) ? [] : this.timodule.modules.filter(function(m) {
if (!m.platform || /^android$/.test(m.platform)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a negative check and return early here so you won't have to wrap everything in the if block.

if (!m.platform || /^android$/.test(m.platform)) {
var localPath = path.join(this.modulesDir, m.id),
globalPath = path.join(this.globalModulesDir, m.id),
getModulePath = function(modulePath) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible do not define functions inside loop-like function like forEach, map or filter and don't use bind as this is expensive. Move it out and pass the module object into it so you can still edit it. Not really an issue here considering the number of modules a project probably has but this will also make the code more readable.

}.bind(this);

if ((fs.existsSync(localPath) && getModulePath(localPath)) ||
(fs.existsSync(globalPath) && getModulePath(globalPath))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to move longer if checks like this into a simple function with a fitting name so it's easier to understand what it does.

@janvennemann
Copy link
Contributor

@garymathews, what about when a modules defines dependencies but they are not present? As i understand it, currently this will just be silently ignored. Maybe we should print a nice error message instead of just continuing the build, knowing that it will probably fail later on anyway.

@jquick-axway
Copy link
Contributor

When the build can't find a module in "tiapp.xml", we see the following error messages logged:

[WARN] :   Could not find a valid Titanium module id=ti.whatever version=latest platform=android deploy-type=test
[ERROR] :  Could not find all required Titanium Modules:
[ERROR] :     id: ti.whatever	 version: latest	 platform: android	 deploy-type: test

If we can't replicate the above for dependency modules as well, then that would be great. (Unless it's doing this already. I haven't tested this code change yet.)

Also, does the build simply log all of the <module> element's attributes if the module hasn't been found? If so, then perhaps as a best-practice we should add a url attribute to it so that developers will know where to download it from.

@garymathews
Copy link
Contributor Author

@janvennemann Updated PR

@garymathews garymathews force-pushed the TIMOB-24722 branch 2 times, most recently from 5c29168 to cc7ff30 Compare October 4, 2017 19:16
Copy link
Collaborator

@hansemannn hansemannn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@garymathews Can you fix the typo in LIBRARY_ORIGIN_PORJECT while you're at it?

Copy link
Contributor

@janvennemann janvennemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved! appc-aar-tools is also updated to 1.1.4

We should probably also make changes to the app build so that it is aware of module dependencies and can print an appropriate error message if a dependency is missing.

@garymathews
Copy link
Contributor Author

@janvennemann Like this?

@janvennemann
Copy link
Contributor

@garymathews Yes, but i mean in the app build. Let's say you use a module that has another dependency then you would also require both modules during the app build, right?

Copy link
Contributor

@janvennemann janvennemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@garymathews, can you please check the linting errors reported by Jenkins? Thanks!

this.cli.tiapp.modules.push({
id: dependency.id,
version: dependency.version,
platform: ['android'],
Copy link
Contributor

@sgtcoolguy sgtcoolguy Nov 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do dependencies always have to be explicitly the same platform and native, or could it be commonjs too?
Do we need to specify the apiversion here to be the same as the requirer/SDK, or is that handled by validateTiModules?

Also, If they specify a version for the dependency, I assume it has to be an exact match, we have no concept of semver here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • commonjs modules aren't supported as module dependencies
  • I tried my best to re-use our current module handling code, so all of the existing module checks will take place. apiversion needs to match the SDK
  • It will be the same as our current module version handling code (I believe it's exact match only?)

@@ -1657,6 +1667,10 @@ process.exit(1);
}
logger.error(msg);
process.exit(1);
*/

// re-validate modules
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a possible infinite loop here? I see before you'd just give up and report missing dependencies, but now we basically implicitly add them for you and re-try.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • It looks like that, but our existing checks will prevent that (if the module is missing/invalid) from happening and return before reaching this code again

@lokeshchdhry
Copy link
Contributor

FR Passed.

We can now use modules using google playservices & module ti.playservices together in an app.

Studio Ver: 4.10.0.201709271713
SDK Ver: 7.0.0.v20171108114616
OS Ver: 10.12.3
Xcode Ver: Xcode 8.3.3
Appc NPM: 4.2.10
Appc CLI: 6.3.0
Ti CLI Ver: 5.0.14
Alloy Ver: 1.10.7
Node Ver: 7.10.1
Java Ver: 1.8.0_101
Devices: ⇨ google Nexus 5 --- Android 6.0.1
⇨ google Nexus 6P --- Android 8.0.0

@lokeshchdhry lokeshchdhry merged commit cff6674 into tidev:master Nov 9, 2017
@benbahrenburg
Copy link
Contributor

@garymathews @jquick-axway is there anything that module developers should be doing to embrace this? For example, how would I add this as a dependency to my module projects. Or is this simply asking the module developers to take the same version of playservices and use it in this modules?

@sgtcoolguy
Copy link
Contributor

@benbahrenburg Here's the PR that makes use of ti.playservices as a dependency of ti.map: tidev/ti.map#216

I think the goal here is to have ti.playservices be a dependency of any native modules requiring it, and then they'll grab the latest ti.playservices module by default (or if you specify an exact version in your tiapp.xml it'll use that). That way we can avoid having all these modules embed differing versions.

@jquick-axway
Copy link
Contributor

@benbahrenburg, we plan on writing up a blog post about this sometime this month to inform 3rd party module developers.

For a more specific example, have a look at our "ti.map" module's "timodule.xml" file (link below) and note the new <modules> section. This is where you define your dependencies.
https://github.com/garymathews/ti.map/blob/9b3c4120cfd907468ca3a598a93d3c48ff49e9d2/android/timodule.xml#L11

So, when a developer includes your module in their project, our build system will double check that their "tiapp.xml" file also references your module's module dependency too. If it doesn't, then the developer will get a build error stating that they need to also include said module dependency into their project. You can see an example of what a "tiapp.xml" file will look like with the "ti.map" module and the new Google Play Service module "ti.playservices" at the top of this pull request.
#9481 (comment)

The plan is to always bundle the new "ti.playservices" dependency module with Titanium so that developer's won't have to download/install it themselves.

@sgtcoolguy
Copy link
Contributor

@jquick-axway Just to note: I asked Gary to modify the behavior of handling module dependencies so that we will attempt to automatically/implicitly add the dependency as if the user had done so in their tiapp.xml (but if they specify it in the tiapp.xml that works too). This was to avoid causing a breaking change to ti.map usage for existing apps (suddenly they'd stop working and the user would have to explicitly add ti.playservices to the app to get it working again).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants