mirror of https://github.com/weisJ/darklaf.git
Browse Source
Migrate to Nokee plugin. Improved dark mode detection on macOS. Fixed issue where the native theme option wasn't available in the theme settings on macOS. Improved scrollbars.pull/188/head
Jannis Weis
5 years ago
committed by
GitHub
45 changed files with 957 additions and 313 deletions
@ -1,16 +1,28 @@ |
|||||||
plugins { |
plugins { |
||||||
`kotlin-dsl` |
`java-gradle-plugin` |
||||||
|
groovy |
||||||
} |
} |
||||||
|
|
||||||
dependencies { |
dependencies { |
||||||
implementation(kotlin("gradle-plugin")) |
implementation(platform("dev.nokee:nokee-gradle-plugins:0.4.0-60cff2b")) |
||||||
} |
} |
||||||
|
|
||||||
repositories { |
repositories { |
||||||
mavenCentral() |
mavenCentral() |
||||||
gradlePluginPortal() |
gradlePluginPortal() |
||||||
|
maven { url = uri("https://dl.bintray.com/nokeedev/distributions") } |
||||||
|
maven { url = uri("https://dl.bintray.com/nokeedev/distributions-snapshots") } |
||||||
} |
} |
||||||
|
|
||||||
configure<KotlinDslPluginOptions> { |
gradlePlugin { |
||||||
experimentalWarning.set(false) |
plugins { |
||||||
|
create("uber-jni-jar") { |
||||||
|
id = "uber-jni-jar" |
||||||
|
implementationClass = "UberJniJarPlugin" |
||||||
|
} |
||||||
|
create("use-prebuilt-binaries") { |
||||||
|
id = "use-prebuilt-binaries" |
||||||
|
implementationClass = "UsePrebuiltBinariesWhenUnbuildablePlugin" |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,14 @@ |
|||||||
|
import java.util.concurrent.Callable |
||||||
|
|
||||||
|
class CallableLogger extends OneTimeLogger implements Callable<List<File>> { |
||||||
|
|
||||||
|
CallableLogger(Runnable logger) { |
||||||
|
super(logger) |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
List<File> call() throws Exception { |
||||||
|
super.log() |
||||||
|
return Collections.emptyList() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,351 @@ |
|||||||
|
import groovy.json.JsonOutput |
||||||
|
import groovy.json.JsonSlurper |
||||||
|
import groovy.transform.CompileStatic |
||||||
|
import org.gradle.api.DefaultTask |
||||||
|
import org.gradle.api.GradleException |
||||||
|
import org.gradle.api.Transformer |
||||||
|
import org.gradle.api.tasks.OutputFile |
||||||
|
|
||||||
|
import java.nio.file.Files |
||||||
|
import java.nio.file.Path |
||||||
|
import java.nio.file.StandardCopyOption |
||||||
|
import java.util.concurrent.locks.ReadWriteLock |
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock |
||||||
|
import java.util.stream.Stream |
||||||
|
import java.util.zip.ZipEntry |
||||||
|
import java.util.zip.ZipFile |
||||||
|
|
||||||
|
@CompileStatic |
||||||
|
class DownloadPrebuiltBinaryFromGitHubAction extends DefaultTask { |
||||||
|
|
||||||
|
private static final ReadWriteLock LOCK = new ReentrantReadWriteLock() |
||||||
|
|
||||||
|
private static final String VERSION_INFO_FILE_NAME = "github_artifact_versions.json" |
||||||
|
private static final String TEMP_PATH = "tmp${File.separator}prebuild" |
||||||
|
private static final String PRE_BUILD_PATH = "libs${File.separator}prebuild" |
||||||
|
|
||||||
|
private final OneTimeLogger tokenWarning = new OneTimeLogger.Static({ |
||||||
|
error("""No github access token is specified. Latest artifacts will need to be included manually. |
||||||
|
|The access token needs to have the 'read-public' property. Specify using: |
||||||
|
| -PgithubAccessToken=<your token> |
||||||
|
|or by setting |
||||||
|
| githubAccessToken=<your token> |
||||||
|
|inside the gradle.properties file. |
||||||
|
|""".stripMargin()) |
||||||
|
}, "missingTokenWarning") |
||||||
|
private final OneTimeLogger useCachedWarning = new OneTimeLogger({ |
||||||
|
log("Could not download artifact or artifact information. Using cached version") |
||||||
|
}) |
||||||
|
|
||||||
|
private Map cacheInfo |
||||||
|
|
||||||
|
private String manualDownloadUrl = "" |
||||||
|
private String user |
||||||
|
private String repository |
||||||
|
private String workflow |
||||||
|
private List<String> branches = [] |
||||||
|
private boolean missingLibraryIsFailure |
||||||
|
|
||||||
|
private String githubAccessToken |
||||||
|
private String variant |
||||||
|
private Optional<File> prebuiltBinary |
||||||
|
|
||||||
|
@OutputFile |
||||||
|
File getPrebuiltBinaryFile() { |
||||||
|
if (user == null) throw new GradleException("Github user isn't specified") |
||||||
|
if (repository == null) repository = project.name |
||||||
|
if (workflow == null) throw new GradleException("Workflow isn't specified") |
||||||
|
|
||||||
|
if (prebuiltBinary == null) { |
||||||
|
if (githubAccessToken == null || githubAccessToken.isEmpty()) { |
||||||
|
tokenWarning.log() |
||||||
|
} |
||||||
|
prebuiltBinary = getExternalBinary(variant) |
||||||
|
} |
||||||
|
|
||||||
|
return prebuiltBinary.orElseGet { |
||||||
|
String errorMessage = """Library for $variant could not be downloaded. |
||||||
|
|Download it from $manualDownloadUrl |
||||||
|
|""".stripMargin() |
||||||
|
|
||||||
|
if (missingLibraryIsFailure) { |
||||||
|
throw new GradleException(format(errorMessage)) |
||||||
|
} else { |
||||||
|
new OneTimeLogger.Static({ |
||||||
|
error(errorMessage) |
||||||
|
}, "${variant}-missing").log() |
||||||
|
} |
||||||
|
return createDirectory(tempFilePath("dummy/")) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void setMissingLibraryIsFailure(boolean missingLibraryIsFailure) { |
||||||
|
this.missingLibraryIsFailure = missingLibraryIsFailure |
||||||
|
} |
||||||
|
|
||||||
|
void setGithubAccessToken(String githubAccessToken) { |
||||||
|
this.githubAccessToken = githubAccessToken |
||||||
|
} |
||||||
|
|
||||||
|
void setVariant(String variant) { |
||||||
|
this.variant = variant |
||||||
|
} |
||||||
|
|
||||||
|
void setUser(String user) { |
||||||
|
this.user = user |
||||||
|
} |
||||||
|
|
||||||
|
void setRepository(String repository) { |
||||||
|
this.repository = repository |
||||||
|
} |
||||||
|
|
||||||
|
void setWorkflow(String workflow) { |
||||||
|
this.workflow = workflow |
||||||
|
} |
||||||
|
|
||||||
|
void setManualDownloadUrl(String manualDownloadUrl) { |
||||||
|
this.manualDownloadUrl = manualDownloadUrl |
||||||
|
} |
||||||
|
|
||||||
|
void setBranches(List<String> branches) { |
||||||
|
this.branches = branches |
||||||
|
} |
||||||
|
|
||||||
|
private Map getCacheInfo() { |
||||||
|
if (cacheInfo == null) { |
||||||
|
LOCK.readLock().lock() |
||||||
|
try { |
||||||
|
File cacheInfoFile = getCacheInfoFile() |
||||||
|
JsonSlurper jsonParser = new JsonSlurper() |
||||||
|
cacheInfo = jsonParser.parseText(cacheInfoFile.text) as Map |
||||||
|
} finally { |
||||||
|
LOCK.readLock().unlock() |
||||||
|
} |
||||||
|
} |
||||||
|
return cacheInfo |
||||||
|
} |
||||||
|
|
||||||
|
private File getCacheInfoFile() { |
||||||
|
String path = preBuildPath(VERSION_INFO_FILE_NAME) |
||||||
|
File cacheInfo = new File(path) |
||||||
|
if (!cacheInfo.exists()) { |
||||||
|
cacheInfo = createFile(path) |
||||||
|
cacheInfo << "{}" |
||||||
|
} |
||||||
|
return cacheInfo |
||||||
|
} |
||||||
|
|
||||||
|
private void writeToCache(String variantName, String timeStamp, File file) { |
||||||
|
LOCK.writeLock().lock() |
||||||
|
try { |
||||||
|
Map cacheInfo = getCacheInfo() |
||||||
|
Map entry = [timeStamp: timeStamp, path: file.absolutePath] |
||||||
|
cacheInfo.put(variantName, entry) |
||||||
|
getCacheInfoFile().write(JsonOutput.prettyPrint(JsonOutput.toJson(cacheInfo))) |
||||||
|
} finally { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Optional<File> getExternalBinary(String variant) { |
||||||
|
Tuple2<Optional<DownloadInfo>, Optional<File>> fetchResult = getBinaryDownloadUrl(variant) |
||||||
|
Optional<DownloadInfo> downloadInfo = fetchResult.getFirst() |
||||||
|
Optional<File> cachedFile = fetchResult.getSecond() |
||||||
|
if (cachedFile.isPresent()) { |
||||||
|
log("Reusing previously downloaded binary ${cachedFile.map { it.absolutePath }.orElse(null)}") |
||||||
|
return cachedFile |
||||||
|
} |
||||||
|
Optional<File> downloadedFile = downloadInfo.map { |
||||||
|
getBinaryFromUrl(variant, it.url).orElse(null) |
||||||
|
} |
||||||
|
|
||||||
|
if (downloadedFile.isPresent()) { |
||||||
|
writeToCache(variant, downloadInfo.get()?.timeStamp, downloadedFile.get()) |
||||||
|
} else { |
||||||
|
info("No file found for variant $variant") |
||||||
|
} |
||||||
|
|
||||||
|
if (downloadedFile.isPresent()) return downloadedFile |
||||||
|
return getCachedFile(variant) |
||||||
|
} |
||||||
|
|
||||||
|
private Optional<File> getBinaryFromUrl(String variant, String url) { |
||||||
|
File directory = createDirectory(preBuildPath(variant)) |
||||||
|
info("Downloading binary for variant '$variant' from $url") |
||||||
|
Optional<File> file = downloadZipFile(url, variant).map { unzip(it, directory).findFirst() }.orElse(Optional.empty()) |
||||||
|
info("Finished download for variant '$variant'") |
||||||
|
return file |
||||||
|
} |
||||||
|
|
||||||
|
private String preBuildPath(String variant) { |
||||||
|
return "${project.buildDir}${File.separator}$PRE_BUILD_PATH${File.separator}$variant" |
||||||
|
} |
||||||
|
|
||||||
|
private Optional<ZipFile> downloadZipFile(String url, String variant) { |
||||||
|
return fetch(url) { |
||||||
|
File file = createFile(zipPath(variant)) |
||||||
|
Path response = file.toPath() |
||||||
|
Files.copy(it.getInputStream(), response, StandardCopyOption.REPLACE_EXISTING) |
||||||
|
return new ZipFile(file) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private String zipPath(String name) { |
||||||
|
return tempFilePath("${name}.zip") |
||||||
|
} |
||||||
|
|
||||||
|
private String tempFilePath(String name) { |
||||||
|
return "$project.buildDir${File.separator}$TEMP_PATH${File.separator}${name}" |
||||||
|
} |
||||||
|
|
||||||
|
private static Stream<File> unzip(ZipFile self, File directory) { |
||||||
|
Collection<ZipEntry> files = self.entries().findAll { !(it as ZipEntry).directory } |
||||||
|
return files.stream().map { |
||||||
|
ZipEntry e = it as ZipEntry |
||||||
|
e.name.with { fileName -> |
||||||
|
File outputFile = createFile("${directory.path}$File.separator$fileName") |
||||||
|
Files.copy(self.getInputStream(e), outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING) |
||||||
|
return outputFile |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Tuple2<Optional<DownloadInfo>, Optional<File>> getBinaryDownloadUrl(String variantName) { |
||||||
|
boolean isUptoDate = false |
||||||
|
File cachedFile = null |
||||||
|
String timeStamp = null |
||||||
|
String artifactUrl = getLatestRun(getJson(getWorkflowsUrl())).with { |
||||||
|
timeStamp = it.get("created_at") |
||||||
|
Optional<String> cachedFilePath = getCachedFilePath(variantName, timeStamp) |
||||||
|
isUptoDate = cachedFilePath.isPresent() |
||||||
|
if (isUptoDate) { |
||||||
|
cachedFile = new File(cachedFilePath.get()) |
||||||
|
isUptoDate = cachedFile.exists() |
||||||
|
} |
||||||
|
return get("artifacts_url") as String |
||||||
|
} |
||||||
|
info("Latest artifact for variant '$variantName' is from $timeStamp") |
||||||
|
if (isUptoDate) { |
||||||
|
return new Tuple2<>(Optional.empty(), Optional.of(cachedFile)) |
||||||
|
} |
||||||
|
DownloadInfo downloadInfo = artifactUrl?.with { url -> |
||||||
|
Map[] artifacts = getJson(url).get("artifacts") as Map[] |
||||||
|
String artifactDownloadUrl = artifacts?.find { variantName == it.get("name") }?.get("url") as String |
||||||
|
return artifactDownloadUrl?.with { |
||||||
|
new DownloadInfo(getJson(it)?.get("archive_download_url") as String, timeStamp) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return new Tuple2<>(Optional.ofNullable(downloadInfo), Optional.empty()) |
||||||
|
} |
||||||
|
|
||||||
|
private String getWorkflowsUrl() { |
||||||
|
return "https://api.github.com/repos/$user/$repository/actions/workflows/$workflow/runs" |
||||||
|
} |
||||||
|
|
||||||
|
private Optional<String> getCachedFilePath(String variantName, String timeStamp) { |
||||||
|
Map cacheInfo = getCacheInfo() |
||||||
|
boolean isLatest = (cacheInfo[variantName] as Map)?.get("timeStamp") == timeStamp |
||||||
|
if (isLatest) { |
||||||
|
return Optional.ofNullable((cacheInfo[variantName] as Map)?.get("path") as String) |
||||||
|
} else { |
||||||
|
return Optional.empty() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Optional<File> getCachedFile(String variant) { |
||||||
|
Map cacheInfo = getCacheInfo() |
||||||
|
return Optional.ofNullable(cacheInfo[variant] as Map).map { |
||||||
|
return new File(String.valueOf(it["path"])).with { |
||||||
|
if (it.exists()) useCachedWarning.log() |
||||||
|
it.exists() ? it : null |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Map getLatestRun(Map json) { |
||||||
|
Map[] runs = json.get("workflow_runs") as Map[] |
||||||
|
return Optional.ofNullable(runs?.find { run -> |
||||||
|
boolean completed = "completed" == run.get("status") |
||||||
|
boolean success = "success" == run.get("conclusion") |
||||||
|
boolean isCorrectBranch = branches.isEmpty() || branches.contains(run.get("head_branch")?.toString()) |
||||||
|
return completed && success && isCorrectBranch |
||||||
|
}).orElseGet { |
||||||
|
log("No suitable workflow run found.") |
||||||
|
return Collections.emptyMap() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Map getJson(String url) { |
||||||
|
return fetch(url) { |
||||||
|
JsonSlurper jsonParser = new JsonSlurper() |
||||||
|
Map parsedJson = jsonParser.parseText(it.getInputStream().getText()) as Map |
||||||
|
return parsedJson |
||||||
|
}.orElse(Collections.emptyMap()) |
||||||
|
} |
||||||
|
|
||||||
|
private <T> Optional<T> fetch(String url, Transformer<T, HttpURLConnection> transformer) { |
||||||
|
info("Fetching $url") |
||||||
|
if (isOffline()) return Optional.empty() |
||||||
|
HttpURLConnection get = new URL(url).openConnection() as HttpURLConnection |
||||||
|
get.setRequestMethod("GET") |
||||||
|
githubAccessToken?.with { |
||||||
|
get.setRequestProperty("Authorization", "token $it") |
||||||
|
} |
||||||
|
try { |
||||||
|
def responseCode = get.getResponseCode() |
||||||
|
if (responseCode == HttpURLConnection.HTTP_OK) { |
||||||
|
return Optional.ofNullable(transformer.transform(get)) |
||||||
|
} else { |
||||||
|
log("Could not fetch $url. Response code '$responseCode'.") |
||||||
|
} |
||||||
|
} catch (IOException ignored) { |
||||||
|
} |
||||||
|
return Optional.empty() |
||||||
|
} |
||||||
|
|
||||||
|
private static File createFile(String fileName) { |
||||||
|
File file = new File(fileName) |
||||||
|
if (file.exists()) file.delete() |
||||||
|
file.getParentFile().mkdirs() |
||||||
|
file.createNewFile() |
||||||
|
return file |
||||||
|
} |
||||||
|
|
||||||
|
private static File createDirectory(String fileName) { |
||||||
|
File file = new File(fileName) |
||||||
|
file.mkdirs() |
||||||
|
return file |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isOffline() { |
||||||
|
return project.getGradle().startParameter.isOffline() |
||||||
|
} |
||||||
|
|
||||||
|
private void info(String message) { |
||||||
|
project.logger.info(format(message)) |
||||||
|
} |
||||||
|
|
||||||
|
private void log(String message) { |
||||||
|
project.logger.warn(format(message)) |
||||||
|
} |
||||||
|
|
||||||
|
private void error(String message) { |
||||||
|
project.logger.error(format(message)) |
||||||
|
} |
||||||
|
|
||||||
|
private String format(String message) { |
||||||
|
String pad = " " * (project.name.size() + 2) |
||||||
|
return "${project.name}: ${message.replace("\n", "\n$pad")}" |
||||||
|
} |
||||||
|
|
||||||
|
private class DownloadInfo { |
||||||
|
protected String url |
||||||
|
protected String timeStamp |
||||||
|
|
||||||
|
private DownloadInfo(String url, String timeStamp) { |
||||||
|
this.url = url |
||||||
|
this.timeStamp = timeStamp |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
import dev.nokee.platform.nativebase.OperatingSystemFamily |
||||||
|
import dev.nokee.platform.nativebase.TargetMachine |
||||||
|
import org.gradle.api.GradleException |
||||||
|
import org.gradle.api.Project |
||||||
|
|
||||||
|
class JniUtils { |
||||||
|
static String asVariantName(TargetMachine targetMachine) { |
||||||
|
String operatingSystemFamily = 'macos' |
||||||
|
if (targetMachine.operatingSystemFamily.windows) { |
||||||
|
operatingSystemFamily = 'windows' |
||||||
|
} |
||||||
|
|
||||||
|
String architecture = 'x86-64' |
||||||
|
if (targetMachine.architecture.'32Bit') { |
||||||
|
architecture = 'x86' |
||||||
|
} |
||||||
|
|
||||||
|
return "$operatingSystemFamily-$architecture" |
||||||
|
} |
||||||
|
|
||||||
|
static String getLibraryFileNameFor(Project project, OperatingSystemFamily osFamily) { |
||||||
|
if (osFamily.windows) { |
||||||
|
return "${project.name}.dll" |
||||||
|
} else if (osFamily.linux) { |
||||||
|
return "lib${project.name}.so" |
||||||
|
} else if (osFamily.macOS) { |
||||||
|
return "lib${project.name}.dylib" |
||||||
|
} |
||||||
|
throw new GradleException("Unknown operating system family '${osFamily}'.") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
class OneTimeLogger { |
||||||
|
private final Runnable logger |
||||||
|
private boolean messageAlreadyLogged = false |
||||||
|
|
||||||
|
OneTimeLogger(Runnable logger) { |
||||||
|
this.logger = logger |
||||||
|
} |
||||||
|
|
||||||
|
protected boolean isLogged() { |
||||||
|
return messageAlreadyLogged |
||||||
|
} |
||||||
|
|
||||||
|
protected void setLogged(boolean logged) { |
||||||
|
messageAlreadyLogged = logged |
||||||
|
} |
||||||
|
|
||||||
|
protected void log() { |
||||||
|
if (!isLogged()) { |
||||||
|
logger.run() |
||||||
|
setLogged(true) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static class Static extends OneTimeLogger { |
||||||
|
|
||||||
|
private static final Map<Object, Boolean> isLogged = new HashMap<>() |
||||||
|
|
||||||
|
private final Object identifier |
||||||
|
|
||||||
|
Static(Runnable logger, Object identifier) { |
||||||
|
super(logger) |
||||||
|
this.identifier = identifier |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void setLogged(boolean logged) { |
||||||
|
isLogged.put(identifier, logged) |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected boolean isLogged() { |
||||||
|
return Boolean.TRUE == isLogged.get(identifier) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
import dev.nokee.platform.jni.JniJarBinary |
||||||
|
import dev.nokee.platform.jni.JniLibrary |
||||||
|
import dev.nokee.platform.jni.JniLibraryExtension |
||||||
|
import dev.nokee.platform.nativebase.TargetMachine |
||||||
|
import groovy.transform.CompileStatic |
||||||
|
import org.gradle.api.Plugin |
||||||
|
import org.gradle.api.Project |
||||||
|
import org.gradle.api.Transformer |
||||||
|
import org.gradle.api.file.CopySpec |
||||||
|
import org.gradle.api.provider.Provider |
||||||
|
import org.gradle.jvm.tasks.Jar |
||||||
|
|
||||||
|
@CompileStatic |
||||||
|
class UberJniJarPlugin implements Plugin<Project> { |
||||||
|
|
||||||
|
@Override |
||||||
|
void apply(Project project) { |
||||||
|
project.tasks.named('jar', Jar) { task -> |
||||||
|
configure(task) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static void configure(Jar task) { |
||||||
|
def project = task.getProject() |
||||||
|
def logger = task.getLogger() |
||||||
|
def library = project.extensions.getByType(JniLibraryExtension) |
||||||
|
library.binaries.withType(JniJarBinary).configureEach { |
||||||
|
if (it.jarTask.isPresent()) it.jarTask.get()?.enabled = false |
||||||
|
} |
||||||
|
if (library.targetMachines.get().size() >= 1) { |
||||||
|
logger.info("${project.name}: Merging binaries into the JVM Jar.") |
||||||
|
for (TargetMachine targetMachine : library.targetMachines.get()) { |
||||||
|
Provider<JniLibrary> variant = library.variants |
||||||
|
.flatMap(targetMachineOf(targetMachine)) |
||||||
|
.map(onlyOne() as Transformer<?, ? super List<?>>) as Provider<JniLibrary> |
||||||
|
task.into(variant.map { it.resourcePath }) { CopySpec spec -> |
||||||
|
spec.from(variant.map { it.nativeRuntimeFiles }) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Filter variants that match the specified target machine. |
||||||
|
private static Transformer<Iterable<JniLibrary>, JniLibrary> targetMachineOf(TargetMachine targetMachine) { |
||||||
|
return new Transformer<Iterable<JniLibrary>, JniLibrary>() { |
||||||
|
@Override |
||||||
|
Iterable<JniLibrary> transform(JniLibrary variant) { |
||||||
|
if (variant.targetMachine == targetMachine) { |
||||||
|
return [variant] |
||||||
|
} |
||||||
|
return [] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure only a single variant is present in the collection and return the variant. |
||||||
|
private static Transformer<JniLibrary, List<? extends JniLibrary>> onlyOne() { |
||||||
|
return new Transformer<JniLibrary, List<? extends JniLibrary>>() { |
||||||
|
@Override |
||||||
|
JniLibrary transform(List<? extends JniLibrary> variants) { |
||||||
|
assert variants.size() == 1 |
||||||
|
return variants.first() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,164 @@ |
|||||||
|
import dev.nokee.platform.jni.JniLibraryExtension |
||||||
|
import groovy.transform.CompileStatic |
||||||
|
import org.gradle.api.Action |
||||||
|
import org.gradle.api.Plugin |
||||||
|
import org.gradle.api.Project |
||||||
|
import org.gradle.api.plugins.ExtensionAware |
||||||
|
|
||||||
|
@CompileStatic |
||||||
|
class UsePrebuiltBinariesWhenUnbuildablePlugin implements Plugin<Project> { |
||||||
|
|
||||||
|
private PrebuildBinariesExtension prebuildExtension |
||||||
|
private GithubArtifactExtension githubArtifactExtension |
||||||
|
|
||||||
|
void prebuildBinaries(Action<? extends PrebuildBinariesExtension> action) { |
||||||
|
action.execute(prebuildExtension) |
||||||
|
} |
||||||
|
|
||||||
|
PrebuildBinariesExtension getPrebuildBinaries() { |
||||||
|
return prebuildExtension |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
void apply(Project project) { |
||||||
|
JniLibraryExtension library = project.extensions.getByType(JniLibraryExtension) |
||||||
|
prebuildExtension = project.extensions.create("prebuildBinaries", PrebuildBinariesExtension, this) |
||||||
|
githubArtifactExtension = (prebuildExtension as ExtensionAware).with { |
||||||
|
it.extensions.create("github", GithubArtifactExtension) |
||||||
|
} |
||||||
|
library.variants.configureEach { var -> |
||||||
|
if (prebuildExtension.alwaysUsePrebuildArtifact || !var.sharedLibrary.buildable) { |
||||||
|
// Try to include the library file... if available |
||||||
|
def defaultLibraryName = JniUtils.getLibraryFileNameFor(project, var.targetMachine.operatingSystemFamily) |
||||||
|
def variantName = JniUtils.asVariantName(var.targetMachine) |
||||||
|
def libraryFile = project.file( |
||||||
|
"${prebuildExtension.prebuildLibrariesFolder}/$variantName/$defaultLibraryName" |
||||||
|
) |
||||||
|
|
||||||
|
if (!libraryFile.exists()) { |
||||||
|
// No local binary provided. Try to download it from github actions. |
||||||
|
def prebuiltBinariesTask = project.tasks.register("downloadPrebuiltBinary$variantName", DownloadPrebuiltBinaryFromGitHubAction.class) |
||||||
|
prebuiltBinariesTask.configure { |
||||||
|
it.githubAccessToken = githubArtifactExtension.accessToken |
||||||
|
it.variant = variantName |
||||||
|
it.user = githubArtifactExtension.user |
||||||
|
it.repository = githubArtifactExtension.repository |
||||||
|
it.workflow = githubArtifactExtension.workflow |
||||||
|
it.manualDownloadUrl = githubArtifactExtension.manualDownloadUrl |
||||||
|
it.branches = githubArtifactExtension.branches |
||||||
|
it.missingLibraryIsFailure = prebuildExtension.missingLibraryIsFailure |
||||||
|
} |
||||||
|
var.nativeRuntimeFiles.setFrom(prebuiltBinariesTask.map { it.prebuiltBinaryFile }) |
||||||
|
var.nativeRuntimeFiles.from(new CallableLogger({ |
||||||
|
project.logger.warn("${project.name}: Using pre-build library from github for targetMachine $variantName.") |
||||||
|
})) |
||||||
|
} else { |
||||||
|
//Use provided library. |
||||||
|
var.nativeRuntimeFiles.setFrom(libraryFile) |
||||||
|
var.nativeRuntimeFiles.from(new CallableLogger({ |
||||||
|
def relativePath = project.rootProject.relativePath(libraryFile) |
||||||
|
project.logger.warn("${project.name}: Using pre-build library $relativePath for targetMachine $variantName.") |
||||||
|
})) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static class PrebuildBinariesExtension { |
||||||
|
|
||||||
|
private String prebuildLibrariesFolder = "pre-build-libraries" |
||||||
|
private boolean alwaysUsePrebuildArtifact = false |
||||||
|
private boolean missingLibraryIsFailure = true |
||||||
|
private UsePrebuiltBinariesWhenUnbuildablePlugin plugin |
||||||
|
|
||||||
|
PrebuildBinariesExtension(UsePrebuiltBinariesWhenUnbuildablePlugin plugin) { |
||||||
|
this.plugin = plugin |
||||||
|
} |
||||||
|
|
||||||
|
void github(Action<? extends GithubArtifactExtension> action) { |
||||||
|
action.execute(plugin.githubArtifactExtension) |
||||||
|
} |
||||||
|
|
||||||
|
void setAlwaysUsePrebuildArtifact(boolean alwaysUsePrebuildArtifact) { |
||||||
|
this.alwaysUsePrebuildArtifact = alwaysUsePrebuildArtifact |
||||||
|
} |
||||||
|
|
||||||
|
boolean getAlwaysUsePrebuildArtifact() { |
||||||
|
return alwaysUsePrebuildArtifact |
||||||
|
} |
||||||
|
|
||||||
|
String getPrebuildLibrariesFolder() { |
||||||
|
return prebuildLibrariesFolder |
||||||
|
} |
||||||
|
|
||||||
|
boolean getMissingLibraryIsFailure() { |
||||||
|
return missingLibraryIsFailure |
||||||
|
} |
||||||
|
|
||||||
|
void setPrebuildLibrariesFolder(String prebuildLibrariesFolder) { |
||||||
|
this.prebuildLibrariesFolder = prebuildLibrariesFolder |
||||||
|
} |
||||||
|
|
||||||
|
void setMissingLibraryIsFailure(boolean missingLibraryIsFailure) { |
||||||
|
this.missingLibraryIsFailure = missingLibraryIsFailure |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static class GithubArtifactExtension { |
||||||
|
private String user |
||||||
|
private String repository |
||||||
|
private String workflow |
||||||
|
private String manualDownloadUrl |
||||||
|
private String accessToken |
||||||
|
private List<String> branches = ["master"] |
||||||
|
|
||||||
|
String getUser() { |
||||||
|
return user |
||||||
|
} |
||||||
|
|
||||||
|
String getRepository() { |
||||||
|
return repository |
||||||
|
} |
||||||
|
|
||||||
|
String getWorkflow() { |
||||||
|
return workflow |
||||||
|
} |
||||||
|
|
||||||
|
String getManualDownloadUrl() { |
||||||
|
return manualDownloadUrl |
||||||
|
} |
||||||
|
|
||||||
|
String getAccessToken() { |
||||||
|
return accessToken |
||||||
|
} |
||||||
|
|
||||||
|
List<String> getBranches() { |
||||||
|
return branches |
||||||
|
} |
||||||
|
|
||||||
|
void setUser(String user) { |
||||||
|
this.user = user |
||||||
|
} |
||||||
|
|
||||||
|
void setRepository(String repository) { |
||||||
|
this.repository = repository |
||||||
|
} |
||||||
|
|
||||||
|
void setWorkflow(String workflow) { |
||||||
|
this.workflow = workflow |
||||||
|
} |
||||||
|
|
||||||
|
void setManualDownloadUrl(String manualDownloadUrl) { |
||||||
|
this.manualDownloadUrl = manualDownloadUrl |
||||||
|
} |
||||||
|
|
||||||
|
void setAccessToken(String accessToken) { |
||||||
|
this.accessToken = accessToken |
||||||
|
} |
||||||
|
|
||||||
|
void setBranches(List<String> branches) { |
||||||
|
this.branches = branches |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,18 +0,0 @@ |
|||||||
import org.gradle.api.DefaultTask |
|
||||||
import org.gradle.api.tasks.Internal |
|
||||||
import org.gradle.api.tasks.TaskAction |
|
||||||
import org.gradle.nativeplatform.toolchain.internal.xcode.MacOSSdkPathLocator |
|
||||||
import java.io.File |
|
||||||
import javax.inject.Inject |
|
||||||
|
|
||||||
open class MacOSSdkPathTask @Inject constructor( |
|
||||||
private val locator: MacOSSdkPathLocator |
|
||||||
) : DefaultTask() { |
|
||||||
@Internal |
|
||||||
lateinit var sdkPath: File |
|
||||||
|
|
||||||
@TaskAction |
|
||||||
fun run() { |
|
||||||
sdkPath = locator.find() |
|
||||||
} |
|
||||||
} |
|
@ -1,126 +0,0 @@ |
|||||||
import org.gradle.internal.jvm.Jvm |
|
||||||
import org.gradle.kotlin.dsl.invoke |
|
||||||
|
|
||||||
plugins { |
|
||||||
`cpp-library` |
|
||||||
`java-library` |
|
||||||
} |
|
||||||
|
|
||||||
// This configuration might be used for adding cpp-only dependencies |
|
||||||
val jniImplementation by configurations.creating |
|
||||||
|
|
||||||
val defaultLibraryName: String by project |
|
||||||
|
|
||||||
configurations.matching { |
|
||||||
it.name.startsWith("cppCompile") || |
|
||||||
it.name.startsWith("nativeLink") || |
|
||||||
it.name.startsWith("nativeRuntime") |
|
||||||
}.all { |
|
||||||
extendsFrom(jniImplementation) |
|
||||||
} |
|
||||||
|
|
||||||
tasks.compileJava { |
|
||||||
options.headerOutputDirectory.convention( |
|
||||||
project.layout.buildDirectory.dir("generated/jni-headers") |
|
||||||
) |
|
||||||
// The nested output is not marked automatically as an output of the task regarding task dependencies. |
|
||||||
// So we mark it manually here. |
|
||||||
// See https://github.com/gradle/gradle/issues/6619. |
|
||||||
outputs.dir(options.headerOutputDirectory) |
|
||||||
// Cannot do incremental header generation, since the pattern for cleaning them up is currently wrong. |
|
||||||
// See https://github.com/gradle/gradle/issues/12084. |
|
||||||
options.isIncremental = false |
|
||||||
} |
|
||||||
|
|
||||||
tasks.withType<CppCompile>().configureEach { |
|
||||||
includes(tasks.compileJava.flatMap { it.options.headerOutputDirectory }) |
|
||||||
} |
|
||||||
|
|
||||||
library { |
|
||||||
binaries.configureEach { |
|
||||||
val targetOs = targetMachine.operatingSystemFamily |
|
||||||
compileTask.get().apply { |
|
||||||
val javaHome = Jvm.current().javaHome.canonicalPath |
|
||||||
includes("$javaHome/include") |
|
||||||
includes(when { |
|
||||||
targetOs.isMacOs -> listOf("$javaHome/include/darwin") |
|
||||||
targetOs.isLinux -> listOf("$javaHome/include/linux") |
|
||||||
targetOs.isWindows -> listOf("$javaHome/include/win32") |
|
||||||
else -> emptyList() |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Gradle does not support [Provider] for [JavaForkOptions.systemProperty], so |
|
||||||
* we pass an object that overrides [Any.toString]. |
|
||||||
*/ |
|
||||||
fun Provider<String>.overrideToString() = object { |
|
||||||
override fun toString() = orNull ?: "" |
|
||||||
} |
|
||||||
|
|
||||||
val TargetMachine.variantName: String get() = "$operatingSystemFamily-$architecture" |
|
||||||
|
|
||||||
// Gradle populates library.binaries in afterEvaluate, so we can't access it earlier |
|
||||||
afterEvaluate { |
|
||||||
// C++ library is built for Windows/macOS only, so we skip it otherwise |
|
||||||
library.developmentBinary.orNull?.let { it as CppSharedLibrary }?.let { developmentBinary -> |
|
||||||
tasks.test { |
|
||||||
dependsOn(developmentBinary.linkTask) |
|
||||||
val libraryDir = developmentBinary.runtimeFile |
|
||||||
.map { it.asFile.parentFile.absolutePath } |
|
||||||
systemProperty("java.library.path", libraryDir.overrideToString()) |
|
||||||
} |
|
||||||
} |
|
||||||
tasks.jar { |
|
||||||
//Disable all task. Tasks are reenabled if needed. |
|
||||||
library.binaries.get().forEach { |
|
||||||
it.compileTask.get().enabled = false |
|
||||||
} |
|
||||||
library.binaries.get() |
|
||||||
.filter { it.isOptimized } |
|
||||||
.filterIsInstance<CppSharedLibrary>().let { |
|
||||||
val taskMap = it.map { binary -> binary.targetPlatform.targetMachine to binary }.toMap() |
|
||||||
library.targetMachines.get().forEach { targetMachine -> |
|
||||||
val libraryPath = "com/github/weisj/darklaf/platform/${project.name}" |
|
||||||
val variantName = targetMachine.variantName |
|
||||||
val libraryFile = file("libraries/$variantName/$defaultLibraryName") |
|
||||||
val relativePath = rootProject.relativePath(libraryFile) |
|
||||||
when { |
|
||||||
libraryFile.exists() -> { |
|
||||||
//Use provided library. |
|
||||||
logger.warn( |
|
||||||
"${project.name}: Using pre-build library $relativePath for targetMachine $variantName." |
|
||||||
) |
|
||||||
into("$libraryPath/$variantName") { |
|
||||||
from(libraryFile) |
|
||||||
} |
|
||||||
} |
|
||||||
targetMachine in taskMap -> { |
|
||||||
taskMap[targetMachine]?.let { binary -> |
|
||||||
binary.compileTask.get().enabled = true |
|
||||||
// Publish optimized binary to reduce size. |
|
||||||
binary.linkTask.get().debuggable.set(false) |
|
||||||
//Build and copy library |
|
||||||
dependsOn(binary.linkTask) |
|
||||||
into("$libraryPath/$variantName") { |
|
||||||
from(binary.runtimeFile) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
else -> { |
|
||||||
val downloadUrl = |
|
||||||
"https://github.com/weisJ/darklaf/actions?query=workflow%3A%22Build+Native+Libraries%22+is%3Asuccess+branch%3Amaster" |
|
||||||
logger.warn( |
|
||||||
""" |
|
||||||
${project.name}: Library $relativePath for targetMachine $variantName does not exist. |
|
||||||
${" ".repeat(project.name.length + 1)} Download it from $downloadUrl |
|
||||||
""".trimIndent() |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,52 +1,51 @@ |
|||||||
|
import JniUtils.asVariantName |
||||||
|
|
||||||
plugins { |
plugins { |
||||||
`jni-library` |
java |
||||||
|
id("dev.nokee.jni-library") |
||||||
|
id("dev.nokee.objective-cpp-language") |
||||||
|
`uber-jni-jar` |
||||||
|
`use-prebuilt-binaries` |
||||||
} |
} |
||||||
|
|
||||||
fun DependencyHandlerScope.javaImplementation(dep: Any) { |
library { |
||||||
compileOnly(dep) |
val minOs = "10.10" |
||||||
runtimeOnly(dep) |
val frameworkVersion = "10.15" |
||||||
} |
|
||||||
|
|
||||||
dependencies { |
dependencies { |
||||||
javaImplementation(project(":darklaf-theme")) |
jvmImplementation(project(":darklaf-theme")) |
||||||
javaImplementation(project(":darklaf-native-utils")) |
jvmImplementation(project(":darklaf-native-utils")) |
||||||
javaImplementation(project(":darklaf-utils")) |
jvmImplementation(project(":darklaf-utils")) |
||||||
javaImplementation(project(":darklaf-platform-base")) |
jvmImplementation(project(":darklaf-platform-base")) |
||||||
javaImplementation(project(":darklaf-property-loader")) |
jvmImplementation(project(":darklaf-property-loader")) |
||||||
|
nativeImplementation("dev.nokee.framework:JavaVM:[$frameworkVersion,)") |
||||||
|
nativeImplementation("dev.nokee.framework:JavaVM:[$frameworkVersion,)") { |
||||||
|
capabilities { |
||||||
|
requireCapability("JavaVM:JavaNativeFoundation:[$frameworkVersion,)") |
||||||
} |
} |
||||||
|
} |
||||||
val macPath by tasks.registering(MacOSSdkPathTask::class) |
nativeImplementation("dev.nokee.framework:AppKit:[$frameworkVersion,)") |
||||||
|
nativeImplementation("dev.nokee.framework:Cocoa:[$frameworkVersion,)") |
||||||
val sdkRoot: Provider<String> get() = macPath.map { it.sdkPath.absolutePath } |
|
||||||
|
|
||||||
fun ListProperty<String>.addJavaFrameworks() { |
|
||||||
addAll("-framework", "JavaNativeFoundation") |
|
||||||
add("-F") |
|
||||||
add(sdkRoot.map { "$it/System/Library/Frameworks/JavaVM.framework/Frameworks" }) |
|
||||||
add("-F") |
|
||||||
add("/System/Library/Frameworks/JavaVM.framework/Frameworks") |
|
||||||
} |
} |
||||||
|
|
||||||
library { |
|
||||||
targetMachines.addAll(machines.macOS.x86_64) |
targetMachines.addAll(machines.macOS.x86_64) |
||||||
binaries.configureEach { |
variants.configureEach { |
||||||
compileTask.get().apply { |
resourcePath.set("com/github/weisj/darklaf/platform/${project.name}/${asVariantName(targetMachine)}") |
||||||
dependsOn(macPath) |
sharedLibrary { |
||||||
compilerArgs.addAll("-x", "objective-c++") |
compileTasks.configureEach { |
||||||
compilerArgs.addAll("-mmacosx-version-min=10.10") |
compilerArgs.addAll("-mmacosx-version-min=$minOs") |
||||||
compilerArgs.addAll("-Wunguarded-availability") |
// Build type not modeled yet, assuming release |
||||||
compilerArgs.addJavaFrameworks() |
compilerArgs.addAll(toolChain.map { |
||||||
source.from( |
when (it) { |
||||||
file("src/main/objectiveCpp/Decorations.mm"), |
is Gcc, is Clang -> listOf("-O2") |
||||||
file("src/main/objectiveCpp/ThemeInfo.mm") |
is VisualCpp -> listOf("/O2") |
||||||
) |
else -> emptyList() |
||||||
} |
} |
||||||
} |
}) |
||||||
binaries.whenElementFinalized(CppSharedLibrary::class) { |
} |
||||||
linkTask.get().apply { |
linkTask.configure { |
||||||
dependsOn(macPath) |
linkerArgs.addAll("-lobjc", "-mmacosx-version-min=$minOs") |
||||||
linkerArgs.addAll("-lobjc", "-framework", "AppKit") |
} |
||||||
linkerArgs.addJavaFrameworks() |
|
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1 +0,0 @@ |
|||||||
defaultLibraryName = libdarklaf-macos.dylib |
|
@ -1,41 +1,54 @@ |
|||||||
|
import JniUtils.asVariantName |
||||||
|
|
||||||
plugins { |
plugins { |
||||||
`jni-library` |
java |
||||||
} |
id("dev.nokee.jni-library") |
||||||
|
id("dev.nokee.cpp-language") |
||||||
fun DependencyHandlerScope.javaImplementation(dep: Any) { |
`uber-jni-jar` |
||||||
compileOnly(dep) |
`use-prebuilt-binaries` |
||||||
runtimeOnly(dep) |
|
||||||
} |
} |
||||||
|
|
||||||
|
library { |
||||||
dependencies { |
dependencies { |
||||||
javaImplementation(project(":darklaf-native-utils")) |
jvmImplementation(project(":darklaf-native-utils")) |
||||||
javaImplementation(project(":darklaf-utils")) |
jvmImplementation(project(":darklaf-utils")) |
||||||
javaImplementation(project(":darklaf-platform-base")) |
jvmImplementation(project(":darklaf-platform-base")) |
||||||
javaImplementation(project(":darklaf-theme")) |
jvmImplementation(project(":darklaf-theme")) |
||||||
javaImplementation(project(":darklaf-property-loader")) |
jvmImplementation(project(":darklaf-property-loader")) |
||||||
javaImplementation("net.java.dev.jna:jna") |
jvmImplementation("net.java.dev.jna:jna") |
||||||
} |
} |
||||||
|
|
||||||
library { |
|
||||||
targetMachines.addAll(machines.windows.x86, machines.windows.x86_64) |
targetMachines.addAll(machines.windows.x86, machines.windows.x86_64) |
||||||
binaries.configureEach { |
variants.configureEach { |
||||||
compileTask.get().compilerArgs.addAll( |
resourcePath.set("com/github/weisj/darklaf/platform/${project.name}/${asVariantName(targetMachine)}") |
||||||
when (toolChain) { |
sharedLibrary { |
||||||
|
compileTasks.configureEach { |
||||||
|
compilerArgs.addAll(toolChain.map { |
||||||
|
when (it) { |
||||||
is Gcc, is Clang -> listOf("--std=c++11") |
is Gcc, is Clang -> listOf("--std=c++11") |
||||||
is VisualCpp -> listOf("/EHsc") |
is VisualCpp -> listOf("/EHsc") |
||||||
else -> emptyList() |
else -> emptyList() |
||||||
} |
} |
||||||
) |
}) |
||||||
|
|
||||||
|
// Build type not modeled yet, assuming release |
||||||
|
compilerArgs.addAll(toolChain.map { |
||||||
|
when (it) { |
||||||
|
is Gcc, is Clang -> listOf("-O2") |
||||||
|
is VisualCpp -> listOf("/O2") |
||||||
|
else -> emptyList() |
||||||
|
} |
||||||
|
}) |
||||||
} |
} |
||||||
binaries.whenElementFinalized(CppSharedLibrary::class) { |
linkTask.configure { |
||||||
linkTask.get().linkerArgs.addAll( |
linkerArgs.addAll(toolChain.map { |
||||||
when (toolChain) { |
when (it) { |
||||||
is Gcc, is Clang -> listOf("-ldwmapi", "-lGdi32", "-luser32", "-ladvapi32", "-Shell32") |
is Gcc, is Clang -> listOf("-ldwmapi", "-lGdi32", "-luser32", "-ladvapi32", "-Shell32") |
||||||
is VisualCpp -> listOf("dwmapi.lib", "user32.lib", "Gdi32.lib", "Advapi32.lib", "Shell32.lib") |
is VisualCpp -> listOf("dwmapi.lib", "user32.lib", "Gdi32.lib", "Advapi32.lib", "Shell32.lib") |
||||||
else -> emptyList() |
else -> emptyList() |
||||||
} |
} |
||||||
) |
}) |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue