/*
* ###
* Android Maven Plugin - android-maven-plugin
*
* Copyright (C) 1999 - 2012 Photon Infotech Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ###
*/
/*
* Copyright (C) 2009 Jayway AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.photon.maven.plugins.android.common;
import static com.photon.maven.plugins.android.common.AndroidExtension.APKLIB;
import static org.apache.maven.RepositoryUtils.toDependency;
import java.io.File;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.sonatype.aether.RepositorySystem;
import org.sonatype.aether.RepositorySystemSession;
import org.sonatype.aether.collection.CollectRequest;
import org.sonatype.aether.graph.Dependency;
import org.sonatype.aether.graph.DependencyFilter;
import org.sonatype.aether.graph.DependencyNode;
import org.sonatype.aether.repository.RemoteRepository;
import org.sonatype.aether.resolution.DependencyRequest;
import org.sonatype.aether.util.filter.AndDependencyFilter;
import org.sonatype.aether.util.filter.ScopeDependencyFilter;
import org.sonatype.aether.util.graph.PreorderNodeListGenerator;
import com.photon.maven.plugins.android.AbstractAndroidMojo;
import com.photon.maven.plugins.android.AndroidNdk;
/**
*/
public class NativeHelper {
public static final int NDK_REQUIRED_VERSION = 7;
private MavenProject project;
private RepositorySystemSession repoSession;
private RepositorySystem repoSystem;
private ArtifactFactory artifactFactory;
private Log log;
private List<RemoteRepository> projectRepos;
public NativeHelper( MavenProject project, List<RemoteRepository> projectRepos, RepositorySystemSession repoSession, RepositorySystem repoSystem, ArtifactFactory artifactFactory, Log log ) {
this.project = project;
this.projectRepos = projectRepos;
this.repoSession = repoSession;
this.repoSystem = repoSystem;
this.artifactFactory = artifactFactory;
this.log = log;
}
public static boolean hasStaticNativeLibraryArtifact(Set<Artifact> resolveNativeLibraryArtifacts) {
for (Artifact resolveNativeLibraryArtifact : resolveNativeLibraryArtifacts) {
if ( "a".equals(resolveNativeLibraryArtifact.getType()) ) {
return true;
}
}
return false;
}
public static boolean hasSharedNativeLibraryArtifact(Set<Artifact> resolveNativeLibraryArtifacts) {
for (Artifact resolveNativeLibraryArtifact : resolveNativeLibraryArtifacts) {
if ( "so".equals(resolveNativeLibraryArtifact.getType()) ) {
return true;
}
}
return false;
}
public Set<Artifact> getNativeDependenciesArtifacts(File unpackDirectory, boolean sharedLibraries) throws MojoExecutionException {
final Set<Artifact> filteredArtifacts = new LinkedHashSet<Artifact>();
// Add all dependent artifacts declared in the pom file
@SuppressWarnings( "unchecked" )
final Set<Artifact> allArtifacts = project.getDependencyArtifacts();
// Add all attached artifacts as well - this could come from the NDK mojo for example
boolean result = allArtifacts.addAll( project.getAttachedArtifacts() );
for ( Artifact artifact : allArtifacts ) {
// A null value in the scope indicates that the artifact has been attached
// as part of a previous build step (NDK mojo)
if ( isNativeLibrary( sharedLibraries, artifact.getType() ) && artifact.getScope() == null ) {
// Including attached artifact
log.debug( "Including attached artifact: "+artifact.getArtifactId()+"("+artifact.getGroupId()+")" );
filteredArtifacts.add( artifact );
} else if ( isNativeLibrary( sharedLibraries, artifact.getType() ) && ( Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) || Artifact.SCOPE_RUNTIME.equals( artifact.getScope() ) ) ) {
filteredArtifacts.add( artifact );
} else if ( APKLIB.equals( artifact.getType() ) ) {
// Check if the artifact contains a libs folder - if so, include it in the list
File libsFolder = new File( AbstractAndroidMojo.getLibraryUnpackDirectory( unpackDirectory, artifact )+"/libs" );
if ( libsFolder.exists() ) {
filteredArtifacts.add( artifact );
}
}
}
Set<Artifact> transientArtifacts = processTransientDependencies( project.getDependencies(), sharedLibraries );
filteredArtifacts.addAll( transientArtifacts );
return filteredArtifacts;
}
private boolean isNativeLibrary( boolean sharedLibraries, String artifactType ) {
return (sharedLibraries ? "so".equals( artifactType ) : "a".equals( artifactType ));
}
private Set<Artifact> processTransientDependencies( List<org.apache.maven.model.Dependency> dependencies, boolean sharedLibraries ) throws MojoExecutionException {
Set<Artifact> transientArtifacts = new LinkedHashSet<Artifact>();
for ( org.apache.maven.model.Dependency dependency : dependencies ) {
if ( !"provided".equals( dependency.getScope() ) && !dependency.isOptional()) {
transientArtifacts.addAll( processTransientDependencies( toDependency(dependency, repoSession.getArtifactTypeRegistry()), sharedLibraries) );
}
}
return transientArtifacts;
}
private Set<Artifact> processTransientDependencies( Dependency dependency, boolean sharedLibraries ) throws MojoExecutionException {
try {
final Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
final CollectRequest collectRequest = new CollectRequest();
collectRequest.setRoot( dependency );
collectRequest.setRepositories( projectRepos );
final DependencyNode node = repoSystem.collectDependencies( repoSession, collectRequest ).getRoot();
final DependencyRequest dependencyRequest = new DependencyRequest( node, new AndDependencyFilter(
new ScopeDependencyFilter( Arrays.asList( "compile", "runtime" ), Arrays.asList( "test" ) ),
// Also exclude any optional dependencies
new DependencyFilter() {
@Override
public boolean accept( DependencyNode dependencyNode, List<DependencyNode> dependencyNodes ) {
return !dependencyNode.getDependency().isOptional();
}
}
));
repoSystem.resolveDependencies( repoSession, dependencyRequest );
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
node.accept( nlg );
final List<Dependency> dependencies = nlg.getDependencies( false );
for ( Dependency dep : dependencies ) {
final org.sonatype.aether.artifact.Artifact depAetherArtifact = dep.getArtifact();
if ( isNativeLibrary( sharedLibraries, depAetherArtifact.getExtension() ) ) {
final Artifact mavenArtifact = artifactFactory.createDependencyArtifact( depAetherArtifact.getGroupId(), depAetherArtifact.getArtifactId(), VersionRange.createFromVersion( depAetherArtifact.getVersion() ), depAetherArtifact.getExtension(), depAetherArtifact.getClassifier(), dep.getScope() );
mavenArtifact.setFile(depAetherArtifact.getFile());
artifacts.add( mavenArtifact );
}
}
return artifacts;
} catch ( Exception e ) {
throw new MojoExecutionException( "Error while processing transient dependencies", e );
}
}
public static void validateNDKVersion(File ndkHomeDir) throws MojoExecutionException {
final File ndkVersionFile = new File(ndkHomeDir, "RELEASE.TXT");
if (!ndkVersionFile.exists()) {
throw new MojoExecutionException("Could not locate RELEASE.TXT in the Android NDK base directory '" + ndkHomeDir.getAbsolutePath() + "'. Please verify your setup! " + AndroidNdk.PROPER_NDK_HOME_DIRECTORY_MESSAGE);
}
try {
String versionStr = FileUtils.readFileToString(ndkVersionFile);
validateNDKVersion(NDK_REQUIRED_VERSION, versionStr);
} catch (Exception e) {
throw new MojoExecutionException("Error while extracting NDK version from '" + ndkVersionFile.getAbsolutePath() + "'. Please verify your setup! " + AndroidNdk.PROPER_NDK_HOME_DIRECTORY_MESSAGE);
}
}
public static void validateNDKVersion(int desiredVersion, String versionStr) throws MojoExecutionException {
int version = 0;
if (versionStr != null) {
versionStr = versionStr.trim();
Pattern pattern = Pattern.compile("[r]([0-9]{1,3})([a-z]{0,1}).*");
Matcher m = pattern.matcher(versionStr);
if (m.matches()) {
final String group = m.group(1);
version = Integer.parseInt(group);
}
}
if (version < desiredVersion) {
throw new MojoExecutionException("You are running an old NDK (version " + versionStr + "), please update to at least r'" + desiredVersion + "' or later");
}
}
}