Hi All, I’d like to solicit feedback on a Gradle plugin that I’m building for the j2objc project, which transpiles (translates) code from Java to Objective-C. The technology was used recently in “Inbox”, the new app from Google. It allowed them to share 70% of the code across Android, iPhone and the web: http://arstechnica.com/information-technology/2014/11/how-google-inbox-shares-70-of-its-code-across-android-ios-and-the-web/
Gist Link on GitHub if you’d like to suggest changes: https://gist.github.com/brunobowden/58d6e311ab96760fc371
The plugin is meant to make it easier for myself and others to use the system. It’s still a work in progress but I would appreciate feedback on style, Gradle conventions and anything else you feel would improve the plugin. At the moment it is only a single script but I want to migrate it to a jar, so that it can include some necessary binaries but can be setup with only a few lines in your build.gradle file. I’ve already done extensive refactoring myself, from dealing with << (broke my code and breaks the forum if surrounded by “”) and discovering “project.afterEvaluate” which greatly simplified the code (thanks Peter for posting about this). In general I’ve been enjoying the technology a great deal, though learning Gradle and Groovy at the same time, is quite a steep learning curve. When writing new code, I do it as if I was writing Python then consult the docs to figure out the Java syntax I need
My questions are scattered through the code as TODOs but here I’ve highlighted the main questions:
Main Questions: 1) Naming conventions on settings and task names? Currently this is “j2objcConfig” for the extension object and for the tasks: “j2objcTranslate”, “j2objcCompile” & “j2objcTest” 2) What’s the best way to package a plugin along with binaries? Pointer to documentation would be sufficient. 3) Android Studio IDE better handles “task Named(type:Exec)” rather than "tasks.create(“Named”, type:Exec). Can I do that here? 4) Should I be using the java plugin’s sourceSets to locate all the project java files? How would I do that? 5) How can I find if the project is using junit and if so, then what version? 6) Anything else you think I should know.
Thanks for a wonderful tool and looking forward to your feedback.
j2objc.gradle:
// TODO(bruno): 'apply' should be done in build.gradle, move there as this becomes a proper plugin
apply plugin: j2objc
class J2objcPluginExtension {
boolean noPackageDirectories = false
String j2objcHome = null
String outputDir = null
}
class j2objc implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("j2objcConfig", J2objcPluginExtension)
// TODO(bruno): split out separate 'main' and 'test' tasks ??
// Wait so that project.j2objcConfig settings have been applied
project.afterEvaluate {
// Setup basic paths
def j2objcHome = System.getenv()['J2OBJC_HOME']
if (project.j2objcConfig.j2objcHome) {
j2objcHome = project.j2objcConfig.j2objcHome
}
def j2objcBin = project.file(j2objcHome + '/j2objc').path
def j2objccBin = project.file(j2objcHome + '/j2objcc').path
def outputDir = project.file(project.buildDir.path + '/j2objc').path
if (project.j2objcConfig.outputDir) {
outputDir = project.file(project.j2objcConfig.outputDir).path
}
def testRunnerFile = project.file(outputDir + '/testrunner')
// TODO(bruno): possible to do??: "task j2objcTranslate(type:Exec) {"
project.tasks.create(name: 'j2objcTranslate', type:Exec) {
description 'Translates all the java source files in to Objective-C using j2objc'
// TODO(bruno): can / should this use sourceSets produced by java plugin?
inputs.files project.files(project.fileTree(
dir: project.projectDir, includes: ['**/*.java']))
outputs.files project.files(inputs.files.collect { file ->
def replaceRegex = project.j2objcConfig.noPackageDirectories ?
// REGEX's REMOVED - they were interfering with the forum layout
def path = project.relativePath(file).replaceFirst(
replaceRegex, project.j2objcConfig.outputDir + '/')
return [path.replace('.java', '.h'), path.replace('.java', '.m')]
}.flatten())
executable = j2objcBin
args '-d', outputDir
// Source path is the same for main and test targets
args '-sourcepath', project.file('src/main/java').path
// TODO(bruno): use junit version specified in j2objc plugin jar
// TODO(bruno): warn if different version than testCompile
args '-classpath', project.file(j2objcHome + '/lib/junit-4.10.jar').path
if (project.j2objcConfig.noPackageDirectories) {
args '--no-package-directories'
}
//
TODO(bruno): add these as options?
//
args ('--prefixes', project.file('src/main/resources/prefixes.properties').path)
//
args '--mapping', project.file('mapping.properties').path
//
args '-use-arc'
inputs.files.each { file ->
args file.path
}
}
project.tasks.create(name: 'j2objcCompile', type:Exec, dependsOn: 'j2objcTranslate') {
description 'Compiles the j2objc generated Objective-C code in to testrunner binary'
inputs.files project.files(project.fileTree(
dir: outputDir, includes: ['**/*.h', '**/*.m']))
outputs.file testRunnerFile
executable j2objccBin
//workingDir buildDir
args ('-I' + outputDir)
args '-ObjC', '-ljunit'
args '-o ', testRunnerFile.path
project.fileTree(dir: outputDir, include: '**/*.m').files.each { i ->
args i.path
}
}
// TODO(bruno): name compileTestJ2objc to match compileTestJava convention?
project.tasks.create(name: 'j2objcTest', type:Exec, dependsOn: 'j2objcCompile') {
description 'Runs all tests in the generated Objective-C code'
inputs.file testRunnerFile
// weirdly this was causing a break in the post
// TODO(bruno): what is the output of tests? How does Gradle track successful tests?
// Generates: com.example.dir1.dir2.ClassTest
def javaTests = project.files(project.fileTree(
dir: project.projectDir, includes: ['**/*Test.java']))
def tests = javaTests.collect { file ->
return [project.relativePath(file)
.replace('src/test/java/', '')
.replace('/', '.')
.replace('.java', '')]
}.flatten()
executable testRunnerFile.path
args 'org.junit.runner.JUnitCore'
args tests
}
}
}
}