| /* |
| * Copyright 2024 The Android Open Source Project |
| * |
| * 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. |
| */ |
| |
| import androidx.build.ProjectLayoutType |
| import org.gradle.api.DefaultTask |
| import org.gradle.api.file.DirectoryProperty |
| import org.gradle.api.provider.Property |
| import org.gradle.api.tasks.Copy |
| import org.gradle.api.tasks.PathSensitivity |
| |
| import javax.inject.Inject |
| |
| /** |
| * Finds the sqlite sources and puts them into the `destinationDirectory`. |
| * |
| * This task is setup differently between AOSP and Github (Playground), hence use the helper |
| * `registerPrepareSqliteSourcesTask` method to create an instance of it. |
| * |
| * On AOSP, the sources are in an external prebuilts repository and simply get copied into the given |
| * [destinationDirectory]. |
| * On Github, they are downloaded from SQLite servers and copied into the `destinationDirectory` |
| * from there. |
| * |
| * To ensure each version is consistent, we use the `sqliteVersion` parameter and check the Sqlite |
| * source code for them. |
| */ |
| abstract class PrepareSqliteSourcesTask extends DefaultTask { |
| // defined in https://github.com/sqlite/sqlite/blob/master/src/sqlite.h.in#L149 |
| private static String VERSION_PREFIX = "#define SQLITE_VERSION" |
| private FileSystemOperations fileSystemOperations |
| |
| /** |
| * The Sqlite version to prepare |
| */ |
| @Input |
| abstract Property<String> getSqliteVersion() |
| |
| /** |
| * The target directory where the Sqlite source will be put |
| */ |
| @OutputDirectory |
| abstract DirectoryProperty getDestinationDirectory() |
| |
| /** |
| * The source directory which includes the original Sqlite amalgamation distribution |
| */ |
| @InputDirectory |
| @PathSensitive(PathSensitivity.NONE) |
| abstract DirectoryProperty getSources() |
| |
| @Inject |
| PrepareSqliteSourcesTask(FileSystemOperations fileSystemOperations) { |
| this.fileSystemOperations = fileSystemOperations |
| description = "Create a directory containing Sqlite sources." |
| group = "build" |
| } |
| |
| @TaskAction |
| void prepareSources() { |
| File originalSqliteSources = sources.asFile.get() |
| validateSqliteVersion(originalSqliteSources) |
| File targetSourceDirectory = destinationDirectory.asFile.get() |
| targetSourceDirectory.deleteDir() |
| targetSourceDirectory.mkdirs() |
| |
| fileSystemOperations.copy { CopySpec copySpec -> |
| copySpec.from(originalSqliteSources) |
| copySpec.into(targetSourceDirectory) |
| copySpec.include("sqlite3.c", "sqlite3.h") |
| } |
| } |
| |
| /** |
| * Finds the sqlite version definition in the source file and ensures it is the same |
| * version as [sqliteVersion] to ensure they never go out of sync. |
| */ |
| private void validateSqliteVersion(File sourceDir) { |
| File headerFile = new File(sourceDir, "sqlite3.h") |
| if (!headerFile.isFile() || !headerFile.canRead()) { |
| throw new IllegalStateException("Cannot find header file at location: ${headerFile}") |
| } |
| String versionLine = headerFile.text.split('\n').find { it.contains(VERSION_PREFIX) } |
| if (versionLine == null) { |
| throw new IllegalStateException("Cannot find the version line in sqlite.") |
| } |
| String strippedVersion = versionLine.takeAfter(VERSION_PREFIX).trim() |
| .takeBetween("\"", "\"") |
| if (strippedVersion != sqliteVersion.get()) { |
| throw new IllegalStateException(""" |
| Expected ${sqliteVersion.get()}, found $strippedVersion. Please update the |
| sqliteVersion parameter if this was intentional. |
| """.trim()) |
| } |
| } |
| } |
| |
| /** |
| * Downloads the sqlite amalgamation for the given version. |
| * See: https://sqlite.org/amalgamation.html and https://www.sqlite.org/download.html for |
| * details. |
| */ |
| @CacheableTask |
| abstract class DownloadSQLiteAmalgamationTask extends DefaultTask { |
| /** |
| * The Sqlite version to download |
| */ |
| @Input |
| abstract Property<String> getReleaseVersion() |
| |
| /** |
| * The year which Sqlite version was released. It is necessary because the download |
| * URL includes the year. |
| */ |
| @Input |
| abstract Property<Integer> getReleaseYear() |
| |
| /** |
| * Target file where the downloaded amalgamation zip file will be written. |
| */ |
| @OutputFile |
| abstract RegularFileProperty getDownloadTargetFile() |
| |
| DownloadSQLiteAmalgamationTask() { |
| description = "Downloads the Sqlite amalgamation build from sqlite servers" |
| group = "build" |
| } |
| |
| @TaskAction |
| void download() { |
| File downloadTarget = downloadTargetFile.asFile.get() |
| downloadTarget.delete() |
| downloadTarget.parentFile.mkdirs() |
| String downloadUrl = buildDownloadUrl(releaseYear.get(), releaseVersion.get()) |
| downloadTarget.withOutputStream { outputStream -> |
| new URL(downloadUrl).withInputStream { inputStream -> |
| inputStream.transferTo(outputStream) |
| } |
| } |
| } |
| |
| /** |
| * Computes the download URL from the sqlite version and release year inputs. |
| */ |
| private static String buildDownloadUrl(int releaseYear, String releaseVersion) { |
| // see https://www.sqlite.org/download.html |
| // The version is encoded so that filenames sort in order of increasing version number |
| // when viewed using "ls". |
| // For version 3.X.Y the filename encoding is 3XXYY00. |
| // For branch version 3.X.Y.Z, the encoding is 3XXYYZZ. |
| def sections = releaseVersion.split('\\.') |
| if (sections.size() < 3) { |
| throw new IllegalArgumentException("Invalid sqlite version $releaseVersion") |
| } |
| int major = sections[0].toInteger() |
| int minor = sections[1].toInteger() |
| int patch = sections[2].toInteger() |
| int branch = sections.size() >= 4 ? sections[3].toInteger() : 0 |
| String fileName = String.format("%d%02d%02d%02d.zip", major, minor, patch, branch) |
| return "https://www.sqlite.org/${releaseYear}/sqlite-amalgamation-${fileName}" |
| } |
| } |
| |
| /** |
| * Configuration object for preparing relevant sqlite sources. |
| */ |
| abstract class Configuration { |
| /** |
| * The Sqlite version to be prepared. |
| */ |
| abstract Property<String> getSqliteVersion() |
| |
| /** |
| * The release year of the requested Sqlite version. |
| * It is necessary because the download URL for sqlite amalgamation includes the |
| * release year. |
| */ |
| abstract Property<Integer> getSqliteReleaseYear() |
| |
| /** |
| * The location to put prepared sqlite sources. |
| */ |
| abstract DirectoryProperty getDestinationDirectory() |
| |
| /** |
| * Set when sqlite is downloaded from prebuilts rather than from Sqlite servers (used in AOSP). |
| */ |
| abstract DirectoryProperty getSqlitePrebuiltsDirectory() |
| } |
| |
| /** |
| * Utility method to create an instance of [PrepareSqliteSourcesTask] that is compatible |
| * with both AOSP and GitHub builds. |
| * This is exported into the build script via ext properties. |
| */ |
| TaskProvider<PrepareSqliteSourcesTask> registerPrepareSqliteSourcesTask( |
| Project project, |
| String name, |
| Action<Configuration> configure |
| ) { |
| def configuration = project.objects.newInstance(Configuration.class) |
| configure.execute(configuration) |
| |
| def distDirectory = project.objects.directoryProperty() |
| if (ProjectLayoutType.isPlayground(project)) { |
| def downloadTaskProvider = project.tasks.register( |
| name.capitalize() + "DownloadAmalgamation", |
| DownloadSQLiteAmalgamationTask |
| ) { |
| it.releaseVersion.set(configuration.sqliteVersion) |
| it.releaseYear.set(configuration.sqliteReleaseYear) |
| it.downloadTargetFile.set( |
| project.layout.buildDirectory.file("sqlite3/download/amalgamation.zip") |
| ) |
| } |
| |
| def unzipTaskProvider = project.tasks.register( |
| name.capitalize() + "UnzipAmalgamation", |
| Copy |
| ) { |
| it.from( |
| project.zipTree(downloadTaskProvider.map { it.downloadTargetFile }) |
| ) |
| it.into( |
| project.layout.buildDirectory.dir("sqlite3/download/unzipped") |
| ) |
| it.eachFile { |
| it.path = it.path.replaceFirst(/sqlite-amalgamation-\d+\//, '') |
| } |
| } |
| distDirectory.set( |
| project.objects.directoryProperty().fileProvider( |
| unzipTaskProvider.map { it.destinationDir } |
| ) |
| ) |
| } else { |
| distDirectory.set(configuration.sqlitePrebuiltsDirectory) |
| } |
| |
| def prepareSourcesTaskProvider = project.tasks.register( |
| name, |
| PrepareSqliteSourcesTask |
| ) { |
| it.sources.set(distDirectory) |
| it.sqliteVersion.set(configuration.sqliteVersion) |
| it.destinationDirectory.set( |
| project.layout.buildDirectory.dir("sqlite/selected-sources") |
| ) |
| } |
| return prepareSourcesTaskProvider |
| } |
| |
| // export a function to register the task |
| ext.registerPrepareSqliteSourcesTask = this.®isterPrepareSqliteSourcesTask |