Skip to content

Commit 0bb8504

Browse files
author
Bernd Zuther
committed
setup macAppBundle for modifications
1 parent a2cf268 commit 0bb8504

File tree

10 files changed

+918
-0
lines changed

10 files changed

+918
-0
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@
44
*.jar
55
*.war
66
*.ear
7+
8+
.gradle/
9+
build/
10+
out/
11+
*.iml
12+
*.ipr
13+
*.iws

build.gradle

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
group "com.github.zutherb.gradle"
2+
3+
apply plugin: 'groovy'
4+
apply plugin: 'idea'
5+
6+
7+
repositories {
8+
mavenCentral()
9+
}
10+
11+
dependencies {
12+
compile gradleApi()
13+
compile localGroovy()
14+
}
15+
16+
task compileJavaAppLuncher(type: Exec) {
17+
def javaAppLuncherDir = "${buildDir}/resources/main/com/github/zutherb/gradle/mapAppBundle"
18+
mkdir javaAppLuncherDir
19+
executable "gcc"
20+
args "-I", "/Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/include",
21+
"-I", "/Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/include/darwin",
22+
"-o", "${javaAppLuncherDir}/JavaAppLauncher",
23+
"-framework", "Cocoa",
24+
"-arch", "x86_64",
25+
"-isysroot", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk",
26+
"-mmacosx-version-min=10.7",
27+
"/Users/berndzuther/develop/workspace/gradle-macappbundle/src/main/object-c/main.m"
28+
}
29+
30+
tasks.processResources.dependsOn(compileJavaAppLuncher)

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
version=1.0.10-SNAPSHOT
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.github.zutherb.gradle.macAppBundle
2+
3+
import org.gradle.api.DefaultTask
4+
import org.gradle.api.tasks.TaskAction
5+
6+
7+
class CopyJavaAppLauncherTask extends DefaultTask {
8+
9+
@TaskAction
10+
def void writeStub() {
11+
def dest = project.file("${project.buildDir}/${project.macAppBundle.appOutputDir}/${project.macAppBundle.appName}.app/Contents/MacOS/${project.macAppBundle.bundleExecutable}")
12+
dest.parentFile.mkdirs()
13+
def outStream = new BufferedOutputStream(new FileOutputStream(dest))
14+
def buf = new byte[1024]
15+
def inStream = this.getClass().getClassLoader().getResourceAsStream("com/github/zuther/gradle/macAppBundle/JavaAppLauncher")
16+
if (inStream == null) throw new RuntimeException("Can't find resource for JavaApplicationStub in jar")
17+
int numRead = inStream.read(buf)
18+
while (numRead > 0) {
19+
outStream.write(buf, 0, numRead)
20+
numRead = inStream.read(buf)
21+
}
22+
inStream.close()
23+
outStream.close()
24+
}
25+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package com.github.zutherb.gradle.macAppBundle
2+
3+
import java.io.File
4+
import java.text.SimpleDateFormat
5+
6+
import org.gradle.api.DefaultTask
7+
import org.gradle.api.InvalidUserDataException
8+
import org.gradle.api.tasks.OutputFile
9+
import org.gradle.api.tasks.TaskAction
10+
11+
import groovy.xml.MarkupBuilder
12+
import org.gradle.api.plugins.JavaPlugin
13+
14+
class GenerateInfoPlistTask extends DefaultTask {
15+
16+
static final String XML_DEF_LINE = '<?xml version="1.0" encoding="UTF-8"?>';
17+
static final String DOCTYPE_LINE = '<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">'
18+
19+
20+
@OutputFile
21+
File getPlistFile() {
22+
return project.macAppBundle.getPlistFileForProject(project)
23+
}
24+
25+
@TaskAction
26+
def void writeInfoPlist() {
27+
MacAppBundlePluginExtension extension = project.macAppBundle
28+
def classpath = project.configurations.runtime.collect { "${it.name}" }
29+
classpath.add(project.tasks[JavaPlugin.JAR_TASK_NAME].outputs.files.getSingleFile().name)
30+
def file = getPlistFile()
31+
file.parentFile.mkdirs()
32+
def writer = new BufferedWriter(new FileWriter(file))
33+
writer.write(XML_DEF_LINE);writer.newLine();
34+
writer.write(DOCTYPE_LINE);writer.newLine();
35+
def xml = new MarkupBuilder(writer)
36+
xml.plist(version:"0.9") {
37+
dict() {
38+
key('CFBundleName')
39+
string(extension.appName)
40+
key('CFBundleIdentifier')
41+
string(extension.mainClassName)
42+
key('CFBundleVersion')
43+
string(project.version)
44+
key('CFBundleAllowMixedLocalizations')
45+
if (extension.bundleAllowMixedLocalizations) { string('true') } else { string('false') }
46+
key('CFBundleExecutable')
47+
string(extension.bundleExecutable)
48+
key('CFBundleDevelopmentRegion')
49+
string(extension.bundleDevelopmentRegion)
50+
key('CFBundlePackageType')
51+
string(extension.bundlePackageType)
52+
key('CFBundleSignature')
53+
string(extension.creatorCode)
54+
key('CFBundleInfoDictionaryVersion')
55+
string(extension.bundleInfoDictionaryVersion)
56+
key('CFBundleIconFile')
57+
string(project.file(extension.icon).name)
58+
key('Java')
59+
dict() {
60+
key('MainClass')
61+
string(extension.mainClassName)
62+
key('JVMVersion')
63+
string(extension.jvmVersion)
64+
key('ClassPath')
65+
doValue(xml, classpath.sort().collect { val -> '$JAVAROOT/'+val })
66+
key('Properties')
67+
dict {
68+
extension.javaProperties.each { k, v->
69+
key("$k")
70+
doValue(xml, v)
71+
}
72+
}
73+
extension.javaExtras.each { k, v->
74+
key("$k")
75+
doValue(xml, v)
76+
}
77+
}
78+
extension.bundleExtras.each { k, v->
79+
key("$k")
80+
doValue(xml, v)
81+
}
82+
if (extension.extras != null) {
83+
xml.getPrinter().with { p -> p.println(extension.extras) }
84+
}
85+
}
86+
}
87+
writer.close()
88+
}
89+
90+
def doValue(xml, value) {
91+
if (value instanceof String) {
92+
xml.string("$value")
93+
} else if (value instanceof Date) {
94+
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
95+
xml.date(sdf.format(value));
96+
//YYYY-MM-DD HH:MM:SS
97+
} else if (value instanceof Short || value instanceof Integer) {
98+
xml.integer(value)
99+
} else if (value instanceof Float || value instanceof Double) {
100+
xml.real(value)
101+
} else if (value instanceof Boolean) {
102+
if (value) {
103+
xml.true()
104+
} else {
105+
xml.false()
106+
}
107+
} else if (value instanceof Map) {
108+
xml.dict {
109+
value.each { subk, subv ->
110+
key("$subk")
111+
doValue(xml, subv)
112+
}
113+
}
114+
} else if (value instanceof List || value instanceof Object[]) {
115+
xml.array {
116+
value.each { subv ->
117+
doValue(xml, subv)
118+
}
119+
}
120+
} else throw new InvalidUserDataException("unknown type for plist: "+value)
121+
}
122+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package com.github.zutherb.gradle.macAppBundle
2+
3+
import org.gradle.api.Project
4+
import org.gradle.api.Plugin
5+
import org.gradle.api.Task
6+
import org.gradle.api.InvalidUserDataException;
7+
import org.gradle.api.file.CopySpec
8+
import org.gradle.api.plugins.JavaPlugin
9+
import org.gradle.api.tasks.Sync;
10+
import org.gradle.api.tasks.Copy;
11+
import org.gradle.api.tasks.Exec
12+
13+
class MacAppBundlePlugin implements Plugin<Project> {
14+
15+
static final String PLUGIN_NAME = "macAppBundle"
16+
static final String GROUP = PLUGIN_NAME
17+
18+
static final String TASK_CONFIGURE_NAME = "configMacApp"
19+
static final String TASK_INFO_PLIST_GENERATE_NAME = "generatePlist"
20+
static final String TASK_PKG_INFO_GENERATE_NAME = "generatePkgInfo"
21+
22+
static final String TASK_LIB_COPY_NAME = "copyToResourcesJava"
23+
static final String TASK_COPY_STUB_NAME = "copyStub"
24+
static final String TASK_COPY_ICON_NAME = "copyIcon"
25+
static final String TASK_SET_FILE_NAME = "runSetFile"
26+
static final String TASK_CREATE_APP_NAME = "createApp"
27+
static final String TASK_CODE_SIGN_NAME = "codeSign"
28+
static final String TASK_CREATE_DMG = "createDmg"
29+
static final String TASK_CREATE_ZIP = "createAppZip"
30+
31+
32+
def void apply(Project project) {
33+
project.plugins.apply(JavaPlugin)
34+
MacAppBundlePluginExtension pluginExtension = new MacAppBundlePluginExtension()
35+
project.extensions.macAppBundle = pluginExtension
36+
37+
Task configTask = addConfigurationTask(project)
38+
Task plistTask = addCreateInfoPlistTask(project)
39+
plistTask.dependsOn(configTask)
40+
Task copyTask = addCopyToLibTask(project)
41+
copyTask.dependsOn(configTask)
42+
Task stubTask = addCopyStubTask(project)
43+
stubTask.dependsOn(configTask)
44+
Task copyIconTask = addCopyIconTask(project)
45+
copyIconTask.dependsOn(configTask)
46+
Task pkgInfoTask = createPkgInfoTask(project)
47+
pkgInfoTask.dependsOn(configTask)
48+
Task createAppTask = addCreateAppTask(project)
49+
createAppTask.dependsOn(plistTask)
50+
createAppTask.dependsOn(copyTask)
51+
createAppTask.dependsOn(stubTask)
52+
createAppTask.dependsOn(copyIconTask)
53+
createAppTask.dependsOn(pkgInfoTask)
54+
Task setFileTask = addSetFileTask(project)
55+
setFileTask.dependsOn(createAppTask)
56+
/* I think setfile is not required for a .app to be run on osx.
57+
* Leaving the task in, but not depended on by anything else. If
58+
* SetFile is needed, then switch the above depends to
59+
createAppTask.dependsOn(setFileTask)
60+
*/
61+
Task codeSignTask = addCodeSignTask(project)
62+
codeSignTask.dependsOn(createAppTask)
63+
Task dmgTask = addDmgTask(project)
64+
dmgTask.dependsOn(createAppTask)
65+
project.getTasksByName("assemble", true).each{ t -> t.dependsOn(dmgTask) }
66+
}
67+
68+
private Task addConfigurationTask(Project project) {
69+
Task task = project.tasks.create(TASK_CONFIGURE_NAME)
70+
task.description = "Sets default configuration values for the extension."
71+
task.group = GROUP
72+
task.doFirst {
73+
project.macAppBundle.configureDefaults(project)
74+
}
75+
return task
76+
}
77+
78+
private Task addCreateInfoPlistTask(Project project) {
79+
Task task = project.tasks.create(TASK_INFO_PLIST_GENERATE_NAME, GenerateInfoPlistTask)
80+
task.description = "Creates the Info.plist configuration file inside the mac osx .app directory."
81+
task.group = GROUP
82+
task.inputs.property("project version", project.version)
83+
task.inputs.property("MacAppBundlePlugin extension", {project.macAppBundle})
84+
task.outputs.file(project.file(project.macAppBundle.getPlistFileForProject(project)))
85+
return task
86+
}
87+
88+
private Task addCopyToLibTask(Project project) {
89+
Sync task = project.tasks.create(TASK_LIB_COPY_NAME, Sync)
90+
task.description = "Copies the project dependency jars in the Contents/Resorces/Java directory."
91+
task.group = GROUP
92+
task.with configureDistSpec(project)
93+
task.into { project.file("${project.buildDir}/${project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app/Contents/Resources/Java") }
94+
return task
95+
}
96+
97+
private Task addCopyStubTask(Project project) {
98+
Task task = project.tasks.create(TASK_COPY_STUB_NAME, CopyJavaAppLauncherTask)
99+
task.description = "Copies the JavaApplicationStub into the Contents/MacOS directory."
100+
task.group = GROUP
101+
task.doLast { ant.chmod(dir: project.file("${project.buildDir}/${project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app/Contents/MacOS"), perm: "755", includes: "*") }
102+
task.inputs.property("bundle executable name", {project.macAppBundle.bundleExecutable})
103+
task.outputs.file("${->project.buildDir}/${->project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app/Contents/MacOS/${project.macAppBundle.bundleExecutable}")
104+
return task
105+
}
106+
107+
private Task addCopyIconTask(Project project) {
108+
Task task = project.tasks.create(TASK_COPY_ICON_NAME, Copy)
109+
task.description = "Copies the icon into the Contents/MacOS directory."
110+
task.group = GROUP
111+
task.from "${->project.macAppBundle.icon}"
112+
task.into "${->project.buildDir}/${->project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app/Contents/Resources"
113+
return task
114+
}
115+
116+
private Task createPkgInfoTask(Project project) {
117+
Task task = project.tasks.create(TASK_PKG_INFO_GENERATE_NAME, PkgInfoTask)
118+
task.description = "Creates the Info.plist configuration file inside the mac osx .app directory."
119+
task.group = GROUP
120+
task.inputs.property("creator code", { project.macAppBundle.creatorCode } )
121+
task.outputs.file(project.macAppBundle.getPkgInfoFileForProject(project))
122+
return task
123+
}
124+
125+
private Task addSetFileTask(Project project) {
126+
def task = project.tasks.create(TASK_SET_FILE_NAME, Exec)
127+
task.description = "Runs SetFile to toggle the magic bit on the .app (probably not needed)"
128+
task.group = GROUP
129+
task.doFirst {
130+
workingDir = project.file("${project.buildDir}/${project.macAppBundle.appOutputDir}")
131+
commandLine "${->project.macAppBundle.setFileCmd}", "-a", "B", "${->project.macAppBundle.appName}.app"
132+
}
133+
task.inputs.dir("${->project.buildDir}/${->project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app")
134+
task.outputs.dir("${->project.buildDir}/${->project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app")
135+
return task
136+
}
137+
138+
private Task addCodeSignTask(Project project) {
139+
def task = project.tasks.create(TASK_CODE_SIGN_NAME, Exec)
140+
task.description = "Runs codesign on the .app (not required)"
141+
task.group = GROUP
142+
task.doFirst {
143+
if ( ! project.macAppBundle.certIdentity) {
144+
throw new InvalidUserDataException("No value has been specified for property certIdentity")
145+
}
146+
workingDir = project.file("${project.buildDir}/${project.macAppBundle.appOutputDir}")
147+
commandLine "${->project.macAppBundle.codeSignCmd}", "-s", "${->project.macAppBundle.certIdentity}", "-f", "${->project.buildDir}/${->project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app"
148+
if (project.macAppBundle.keyChain) {
149+
commandLine << "--keychain" << "${->project.macAppBundle.keyChain}"
150+
}
151+
}
152+
task.inputs.dir("${->project.buildDir}/${->project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app")
153+
task.outputs.dir("${->project.buildDir}/${->project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app")
154+
return task
155+
}
156+
157+
private Task addDmgTask(Project project) {
158+
def task = project.tasks.create(TASK_CREATE_DMG, Exec)
159+
task.description = "Create a dmg containing the .app"
160+
task.group = GROUP
161+
task.doFirst {
162+
workingDir = project.file("${project.buildDir}/${project.macAppBundle.dmgOutputDir}")
163+
commandLine "hdiutil", "create", "-srcfolder",
164+
project.file("${project.buildDir}/${project.macAppBundle.appOutputDir}"),
165+
"-volname", "${->project.macAppBundle.volumeName}",
166+
"${->project.macAppBundle.dmgName}"
167+
def dmgFile = project.file("${project.buildDir}/${project.macAppBundle.dmgOutputDir}/${->project.macAppBundle.dmgName}.dmg")
168+
if (dmgFile.exists()) dmgFile.delete()
169+
}
170+
task.inputs.dir("${->project.buildDir}/${->project.macAppBundle.appOutputDir}/${->project.macAppBundle.appName}.app")
171+
task.outputs.file("${->project.buildDir}/${project.macAppBundle.dmgOutputDir}/${->project.macAppBundle.dmgName}.dmg")
172+
task.doFirst { task.outputs.files.each { it.delete() } }
173+
task.doFirst { project.file("${->project.buildDir}/${project.macAppBundle.dmgOutputDir}").mkdirs()}
174+
return task
175+
}
176+
177+
private Task addCreateAppTask(Project project) {
178+
def task = project.tasks.create(TASK_CREATE_APP_NAME)
179+
task.description = "Placeholder task for tasks relating to creating .app applications"
180+
task.group = GROUP
181+
return task
182+
}
183+
184+
private CopySpec configureDistSpec(Project project) {
185+
CopySpec distSpec = project.copySpec {}
186+
def jar = project.tasks[JavaPlugin.JAR_TASK_NAME]
187+
188+
distSpec.with {
189+
from(jar)
190+
from(project.configurations.runtime)
191+
}
192+
193+
return distSpec
194+
}
195+
}
196+
197+
198+
199+
200+

0 commit comments

Comments
 (0)