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

Feature/android support #17

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ build

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@ jdk:
- openjdk8
install: true

env:
- ANDROID_HOME=$HOME/android-sdk

before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
- $ANDROID_HOME

script:
- ./prepareAndroidHome.sh
- ./gradlew -PnewVersion="$TRAVIS_TAG" --version
- ./gradlew -PnewVersion="$TRAVIS_TAG" clean
- ./gradlew -PnewVersion="$TRAVIS_TAG" build
- ./gradlew -PnewVersion="$TRAVIS_TAG" check
Expand Down
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@ if (project.hasProperty('newVersion')) {
project.version = '1.0.33-SNAPSHOT'
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

repositories {
mavenCentral()
mavenLocal()
google()
jcenter()
}

dependencies {
Expand All @@ -36,6 +43,8 @@ dependencies {
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.2")
testImplementation gradleTestKit()
testImplementation 'org.assertj:assertj-core:3.15.0'
testRuntimeOnly("com.android.tools.build:gradle:4.0.1")
testRuntimeOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72")
}

test {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#Fri May 01 20:33:27 CEST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
Expand Down
9 changes: 9 additions & 0 deletions prepareAndroidHome.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh

if [ ! -d $ANDROID_HOME ]; then
mkdir -p $ANDROID_HOME
echo "sdk.dir=$ANDROID_HOME" > local.properties

mkdir $ANDROID_HOME/licenses
echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > $ANDROID_HOME/licenses/android-sdk-license
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.github.roroche.plantuml.android

import org.gradle.api.Project

/***
* Class to determine if a project has an Android plugin applied
*/
class AndroidProjectType {
private AndroidProjectType() {

}

static boolean isAndroidProject(Project project) {
Copy link
Owner

Choose a reason for hiding this comment

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

I target to don't have any static method in the codebase. Here is why: https://www.yegor256.com/2017/02/07/private-method-is-new-class.html

But looking at the source code you suggest, I guess that we could try something:

interface SuppertedProject {
  fun sourceUrls(): Urls

  abstract class Wrap(delegate: SuppertedProject) : SuppertedProject by delegate
}

class DefaultProject: SuppertedProject  {
  // do classical stuff
}

class AndroidLibrary: SuppertedProject {
  // do specific Android library stuff
}

class AndroidApplication: SuppertedProject {
  // do specific Android library stuff
}

// a impl that can determine the proper project impl to use or throw
class SmartProject(project: Project) : SupportedProject.Wrap(
  delegate = when(project) {
    project.pluginManager.hasPlugin("com.android.application") -> AndroidApplication()
    project.pluginManager.hasPlugin("com.android.library") -> AndroidLibrary()
    else -> DefaultProject()
  }
)

return isAndroidApplication(project) || isAndroidLibrary(project)
}

static boolean isAndroidApplication(Project project) {
return project.pluginManager.hasPlugin("com.android.application")
}

static boolean isAndroidLibrary(Project project) {
return project.pluginManager.hasPlugin("com.android.library")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import com.github.roroche.plantuml.diagrams.ClassDiagram
import com.github.roroche.plantuml.diagrams.Diagram
import com.github.roroche.plantuml.diagrams.DiagramWithLog
import com.github.roroche.plantuml.urls.CompileClasspathUrls
import com.github.roroche.plantuml.urls.CompileClasspathUrlsFactory
import com.github.roroche.plantuml.urls.FilesUrls
import com.github.roroche.plantuml.urls.LoggableUrls
import com.github.roroche.plantuml.urls.MainOutputUrls
import com.github.roroche.plantuml.urls.MainOutputUrlsFactory
import com.github.roroche.plantuml.urls.MergedUrls
import org.gradle.api.DefaultTask
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction

Expand Down Expand Up @@ -78,6 +82,12 @@ class BuildClassDiagramTask extends DefaultTask implements CustomTask {
new ClassDiagram(classes),
logger
)

def outputFileParent = extension.outputFile.parentFile
if (!outputFileParent.exists()) {
outputFileParent.mkdirs()
}

diagram.print(extension.outputFile)
}

Expand All @@ -86,13 +96,13 @@ class BuildClassDiagramTask extends DefaultTask implements CustomTask {
return new URLClassLoader(
new MergedUrls(
Arrays.asList(
new LoggableUrls(
new CompileClasspathUrls(project),
new LoggableUrls(
CompileClasspathUrlsFactory.createCompileClasspathUrls(project),
"CompileClasspath",
logger
),
new LoggableUrls(
new MainOutputUrls(project),
MainOutputUrlsFactory.createMainOutputUrls(project),
"MainOutput",
logger
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CompileClasspathUrls extends Urls.Wrap {
CompileClasspathUrls(final Project project) {
this(
new FilesUrls(
project.configurations["compileClasspath"].files
project.configurations["compileClasspath"].files
)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.github.roroche.plantuml.urls

import org.gradle.api.Project

import static com.github.roroche.plantuml.android.AndroidProjectType.*

/**
* Factory for creating the correct classpath based on the type of a project
*/
class CompileClasspathUrlsFactory {
Copy link
Owner

Choose a reason for hiding this comment

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

Pretty much the same comment, for these reasons:

So you can move the logic to the SupportedProject suggested above.

static Urls.Wrap createCompileClasspathUrls(Project project) {
Copy link
Owner

Choose a reason for hiding this comment

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

Please use the high-level type, which is Urls in this case. The motivation is to always work by contract: https://www.yegor256.com/2014/11/20/seven-virtues-of-good-object.html#2-he-works-by-contracts

if (isAndroidProject(project)) {
def classpath = []

project.android.getBootClasspath().each {
classpath.add(it)
}

def debugVariant

if (isAndroidLibrary(project)) {
debugVariant = project.android.libraryVariants.find {
it.name.contains("debug") || it.name.contains("Debug")
}
} else {
debugVariant = project.android.applicationVariants.find {
it.name.contains("debug") || it.name.contains("Debug")
}
}
Copy link
Owner

Choose a reason for hiding this comment

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

so this logic will also go to the dedicated Android's SupportedProject.


classpath.addAll(debugVariant.javaCompileProvider.get().classpath.files)

return new CompileClasspathUrls(new FilesUrls(classpath.toSet()))
} else {
return new CompileClasspathUrls(project)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.github.roroche.plantuml.urls

import org.gradle.api.Project

import static com.github.roroche.plantuml.android.AndroidProjectType.*

/***
* Factory for creating the correct output URLs based on the type of a project
*/
class MainOutputUrlsFactory {
static Urls.Wrap createMainOutputUrls(Project project) {
Copy link
Owner

Choose a reason for hiding this comment

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

So this logic can go to a KotlinProject: SupportedProject and it will be composed such as:

class KotlinProject(private val project: Project): SupportedProject {
  fun sourceUrls(): Urls {
    def debugTask = project.tasks.find {
      it.name.contains("compile") && it.name.contains("DebugKotlin")
    }
    return new MainOutputUrls(
      new FilesUrls(
        [debugTask.destinationDir].toSet()
      )
    )
  }
}

class AndroidApplication(project: Project): SupportedProject.Wrap(
  delegate = KotlinProject(project)
) {
  fun sourceUrls(): Urls {
    val ktSrcUrls = super.sourceUrls()
    // return the specific list + ktSrcUrls 
  }
}

if (isAndroidProject(project)) {
def debugTask = project.tasks.find {
it.name.contains("compile") && it.name.contains("DebugKotlin")
}

return new MainOutputUrls(
new FilesUrls(
[debugTask.destinationDir].toSet()
)
)
} else {
return new MainOutputUrls(project)
Copy link
Owner

Choose a reason for hiding this comment

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

this logic goes to the default DefaultProject implementation.

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.github.roroche.plantuml.android

import com.github.roroche.plantuml.urls.MainOutputUrlsFactory
import com.github.roroche.plantuml.urls.Urls
import org.gradle.api.Project
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.Test

class AndroidProjectTypeTest {
private final Project project = ProjectBuilder.builder().build()

@Test
void testProjectIsAndroidApplication() {
project.apply plugin: "com.android.application"
project.android {
compileSdkVersion 21
}
project.evaluate()
Copy link
Owner

Choose a reason for hiding this comment

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

I think we can reproduce the mechanism found here: https://github.com/RoRoche/plantuml-gradle-plugin/blob/master/src/test/groovy/com/github/roroche/plantuml/BuiltProject.groovy

I suggest a new interface such as:

interface TestableProject {
  fun toProject(): Project
}

and then you can put the logic to prepare a project such as:

class TestableAndroidApplication: TestableProject {
  fun toProject(): Project {
    ProjectBuilder.builder().build()
    project.apply plugin: "com.android.application"
    project.android {
      compileSdkVersion 21
    }
    project.evaluate()
    return project
  }
}

This will remove duplicate code in test.


assert AndroidProjectType.isAndroidProject(project)
assert AndroidProjectType.isAndroidApplication(project)
assert !AndroidProjectType.isAndroidLibrary(project)
Copy link
Owner

Choose a reason for hiding this comment

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

Copy link
Owner

Choose a reason for hiding this comment

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

But maybe these tests are not worth considering now and we should focus the effort on the next ones (those about Urls generation)

}

@Test
void testProjectIsAndroidLibrary() {
project.apply plugin: "com.android.library"
project.android {
compileSdkVersion 21
}
project.evaluate()

assert AndroidProjectType.isAndroidProject(project)
assert !AndroidProjectType.isAndroidApplication(project)
assert AndroidProjectType.isAndroidLibrary(project)
}

@Test
void testProjectIsNotAndroid() {
assert !AndroidProjectType.isAndroidProject(project)
assert !AndroidProjectType.isAndroidApplication(project)
assert !AndroidProjectType.isAndroidLibrary(project)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.github.roroche.plantuml.urls

import org.gradle.api.Project
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.Test

class CompileClasspathUrlsFactoryTest {

Project project

CompileClasspathUrlsFactoryTest() {
project = ProjectBuilder.builder().build()
}

@Test
void testJavaProjectShouldReturnCorrectClassPathForAndroidAppProject() {
project.apply plugin: "com.android.application"
project.android {
compileSdkVersion 21
}
project.evaluate()

Urls.Wrap urls = CompileClasspathUrlsFactory.createCompileClasspathUrls(project)

def files = []

urls.toList().each { url ->
files.add(new File(url.toURI()).getName())
}

assert files.contains("android.jar")
assert files.contains("R.jar")
}

@Test
void testJavaProjectShouldReturnCorrectClassPathForAndroidLibraryProject() {
project.apply plugin: "com.android.library"
project.android {
compileSdkVersion 21
}
project.evaluate()

Urls.Wrap urls = CompileClasspathUrlsFactory.createCompileClasspathUrls(project)

def files = []

urls.toList().each { url ->
files.add(new File(url.toURI()).getName())
}

assert files.contains("android.jar")
assert files.contains("R.jar")
}
}
Loading