|
| 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