-
Notifications
You must be signed in to change notification settings - Fork 74
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
Embed the Zipline app in Android app assets #1563
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,9 +24,11 @@ import org.gradle.accessors.dm.LibrariesForLibs | |
import org.gradle.api.JavaVersion | ||
import org.gradle.api.Plugin | ||
import org.gradle.api.Project | ||
import org.gradle.api.attributes.Attribute | ||
import org.gradle.api.plugins.JavaApplication | ||
import org.gradle.api.publish.PublishingExtension | ||
import org.gradle.api.publish.maven.MavenPublication | ||
import org.gradle.api.tasks.bundling.Zip | ||
import org.gradle.api.tasks.compile.JavaCompile | ||
import org.gradle.api.tasks.testing.AbstractTestTask | ||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL | ||
|
@@ -344,7 +346,7 @@ private class RedwoodBuildExtensionImpl(private val project: Project) : RedwoodB | |
} | ||
} | ||
|
||
override fun application(name: String, mainClass: String) { | ||
override fun cliApplication(name: String, mainClass: String) { | ||
project.plugins.apply("application") | ||
|
||
val application = project.extensions.getByName("application") as JavaApplication | ||
|
@@ -374,4 +376,74 @@ private class RedwoodBuildExtensionImpl(private val project: Project) : RedwoodB | |
} | ||
} | ||
} | ||
|
||
override fun ziplineApplication() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I aught to move this behavior to Zipline itself! |
||
var hasZipline = false | ||
project.afterEvaluate { | ||
check(hasZipline) { | ||
"Project ${project.path} must have Zipline plugin to create Zipline application" | ||
} | ||
} | ||
project.plugins.withId("app.cash.zipline") { | ||
hasZipline = true | ||
|
||
val ziplineConfiguration = project.configurations.create("zipline") { | ||
it.isVisible = false | ||
it.isCanBeResolved = false | ||
it.isCanBeConsumed = true | ||
it.attributes { | ||
it.attribute(ziplineAttribute, ziplineAttributeValue) | ||
} | ||
} | ||
|
||
// Only a single file can be used as an artifact so zip up the compiled contents. | ||
val zipTask = project.tasks.register("zipProductionZiplineFiles", Zip::class.java) { | ||
// Note: This makes assumptions about our setup having a JS target with the default name. | ||
it.from(project.tasks.named("compileProductionExecutableKotlinJsZipline")) | ||
it.destinationDirectory.set(project.layout.buildDirectory.dir("libs")) | ||
it.archiveClassifier.set("zipline") | ||
} | ||
|
||
project.artifacts.add(ziplineConfiguration.name, zipTask) | ||
} | ||
} | ||
|
||
override fun embedZiplineApplication(dependencyNotation: Any, name: String) { | ||
var hasApplication = false | ||
project.afterEvaluate { | ||
check(hasApplication) { | ||
"Project ${project.path} must have Android Application plugin to embed Zipline application" | ||
} | ||
} | ||
project.plugins.withId("com.android.application") { | ||
hasApplication = true | ||
|
||
// Note: This will crash if you call it twice. We don't need this today, so it's not supported. | ||
val ziplineConfiguration = project.configurations.create("zipline") { | ||
it.isVisible = false | ||
it.isCanBeResolved = true | ||
it.isCanBeConsumed = false | ||
it.attributes { | ||
it.attribute(ziplineAttribute, ziplineAttributeValue) | ||
} | ||
} | ||
project.dependencies.add(ziplineConfiguration.name, dependencyNotation) | ||
|
||
val task = project.tasks.register("copyEmbeddedZiplineApplication", ZiplineAppAssetCopyTask::class.java) { | ||
it.appName.set(name) | ||
it.files.setFrom(ziplineConfiguration) | ||
} | ||
|
||
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) | ||
androidComponents.onVariants { | ||
val assets = checkNotNull(it.sources.assets) { | ||
"Project ${project.path} assets must be enabled to embed Zipline application" | ||
} | ||
assets.addGeneratedSourceDirectory(task, ZiplineAppAssetCopyTask::outputDirectory) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private val ziplineAttribute = Attribute.of("zipline", String::class.java) | ||
private const val ziplineAttributeValue = "yep" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Copyright (C) 2023 Square, 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. | ||
*/ | ||
package app.cash.redwood.buildsupport | ||
|
||
import app.cash.zipline.ZiplineManifest | ||
import javax.inject.Inject | ||
import org.gradle.api.DefaultTask | ||
import org.gradle.api.file.ArchiveOperations | ||
import org.gradle.api.file.ConfigurableFileCollection | ||
import org.gradle.api.file.DirectoryProperty | ||
import org.gradle.api.provider.Property | ||
import org.gradle.api.tasks.Input | ||
import org.gradle.api.tasks.InputFiles | ||
import org.gradle.api.tasks.OutputDirectory | ||
import org.gradle.api.tasks.TaskAction | ||
|
||
private const val ZIPLINE_MANIFEST_JSON = "manifest.zipline.json" | ||
|
||
internal abstract class ZiplineAppAssetCopyTask | ||
@Inject constructor( | ||
private val archiveOperations: ArchiveOperations, | ||
) : DefaultTask() { | ||
@get:InputFiles | ||
abstract val files: ConfigurableFileCollection | ||
|
||
@get:Input | ||
abstract val appName: Property<String> | ||
|
||
@get:OutputDirectory | ||
abstract val outputDirectory: DirectoryProperty | ||
|
||
@TaskAction | ||
fun execute() { | ||
val outputDirectory = outputDirectory.get() | ||
outputDirectory.asFile.apply { | ||
deleteRecursively() | ||
mkdirs() | ||
} | ||
|
||
val fileMap = archiveOperations.zipTree(files.singleFile) | ||
.files | ||
.associateBy { it.name } | ||
.toMutableMap() | ||
|
||
val inputManifestFile = checkNotNull(fileMap.remove(ZIPLINE_MANIFEST_JSON)) { | ||
"No zipline.manifest.json file found in input files ${fileMap.keys}" | ||
} | ||
val inputManifest = ZiplineManifest.decodeJson(inputManifestFile.readText()) | ||
|
||
// Add a timestamp to the manifest which is required for Zipline to load as an embedded app. | ||
val outputManifest = inputManifest.copy( | ||
unsigned = inputManifest.unsigned.copy( | ||
freshAtEpochMs = System.currentTimeMillis(), | ||
), | ||
) | ||
val outputManifestFile = outputDirectory.file("${appName.get()}.$ZIPLINE_MANIFEST_JSON").asFile | ||
outputManifestFile.writeText(outputManifest.encodeJson()) | ||
|
||
// Rewrite all .zipline files to their SHA-256 hashes which is how Zipline loads when embedded. | ||
for (module in outputManifest.modules.values) { | ||
val inputFile = checkNotNull(fileMap.remove(module.url)) { | ||
"No ${module.url} file found in input files as specified by the manifest" | ||
} | ||
val outputFile = outputDirectory.file(module.sha256.hex()).asFile | ||
inputFile.copyTo(outputFile) | ||
} | ||
Comment on lines
+63
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Future change: going to move all this to happen on the production-side rather than the consumer-side so as to support iOS more easily. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And move this to the Zipline Gradle plugin! |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like how this plugin is structured