strip old extensions
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
/build/
|
||||
/coverage/
|
||||
.gradle/
|
||||
@@ -1,30 +0,0 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "20f82749394e68bcfbbeee96bad384abaae09c13"
|
||||
channel: "stable"
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
|
||||
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
|
||||
- platform: android
|
||||
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
|
||||
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,15 +0,0 @@
|
||||
# pluggable_transports_proxy
|
||||
|
||||
A new Flutter plugin project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter
|
||||
[plug-in package](https://flutter.dev/to/develop-plugins),
|
||||
a specialized package that includes platform-specific implementation code for
|
||||
Android and/or iOS.
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lint/package.yaml
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
linter:
|
||||
rules:
|
||||
unawaited_futures: true
|
||||
discarded_futures: true
|
||||
collection_methods_unrelated_type: true
|
||||
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
||||
exclude:
|
||||
- "**.g.dart"
|
||||
- "**.swagger.dart"
|
||||
- "**.freezed.dart"
|
||||
- "**.chopper.dart"
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -1,9 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
@@ -1,70 +0,0 @@
|
||||
group = "eu.weblibre.pluggable_transports_proxy"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = "2.1.20"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.13.2")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.android.library"
|
||||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
namespace = "eu.weblibre.pluggable_transports_proxy"
|
||||
|
||||
compileSdk = 36
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += "src/main/kotlin"
|
||||
test.java.srcDirs += "src/test/kotlin"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.mockito:mockito-core:5.0.0")
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.netzarchitekten:IPtProxy:4.3.0'
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
rootProject.name = 'pluggable_transports_proxy'
|
||||
@@ -1,3 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="eu.weblibre.pluggable_transports_proxy">
|
||||
</manifest>
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2025 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package eu.weblibre.pluggable_transports_proxy
|
||||
|
||||
import IPtProxy.Controller
|
||||
import eu.weblibre.pluggable_transports_proxy.pigeons.IPtProxyController
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import java.io.File
|
||||
|
||||
/** PluggableTransportsProxyPlugin */
|
||||
class PluggableTransportsProxyPlugin : FlutterPlugin {
|
||||
private lateinit var channel: MethodChannel
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: Controller? = null
|
||||
|
||||
fun getController(ptDirPath: String): Controller {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: Controller(ptDirPath, true, false, "INFO", null).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
val ptDir = File(flutterPluginBinding.applicationContext.cacheDir, "pt_state")
|
||||
val ptc = getController(ptDir.path)
|
||||
|
||||
IPtProxyController.setUp(flutterPluginBinding.binaryMessenger, ProxyImpl(controller = ptc))
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
// Controller instance remains alive for reuse
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2025 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package eu.weblibre.pluggable_transports_proxy
|
||||
|
||||
import eu.weblibre.pluggable_transports_proxy.pigeons.IPtProxyController
|
||||
import eu.weblibre.pluggable_transports_proxy.pigeons.ProxyType
|
||||
|
||||
class ProxyImpl(val controller: IPtProxy.Controller) : IPtProxyController {
|
||||
override fun start(
|
||||
proxyType: ProxyType,
|
||||
proxy: String
|
||||
): Long {
|
||||
val type = when (proxyType) {
|
||||
ProxyType.OBFS4 -> IPtProxy.IPtProxy.Obfs4
|
||||
ProxyType.MEEK_LITE -> IPtProxy.IPtProxy.MeekLite
|
||||
ProxyType.WEBTUNNEL -> IPtProxy.IPtProxy.Webtunnel
|
||||
ProxyType.SNOWFLAKE -> IPtProxy.IPtProxy.Snowflake
|
||||
};
|
||||
|
||||
controller.start(type, proxy)
|
||||
|
||||
return controller.port(type)
|
||||
}
|
||||
|
||||
override fun stop(proxyType: ProxyType) {
|
||||
val type = when (proxyType) {
|
||||
ProxyType.OBFS4 -> IPtProxy.IPtProxy.Obfs4
|
||||
ProxyType.MEEK_LITE -> IPtProxy.IPtProxy.MeekLite
|
||||
ProxyType.WEBTUNNEL -> IPtProxy.IPtProxy.Webtunnel
|
||||
ProxyType.SNOWFLAKE -> IPtProxy.IPtProxy.Snowflake
|
||||
};
|
||||
|
||||
controller.stop(type)
|
||||
}
|
||||
}
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
// Autogenerated from Pigeon (v26.1.5), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||
|
||||
package eu.weblibre.pluggable_transports_proxy.pigeons
|
||||
|
||||
import android.util.Log
|
||||
import io.flutter.plugin.common.BasicMessageChannel
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.MessageCodec
|
||||
import io.flutter.plugin.common.StandardMethodCodec
|
||||
import io.flutter.plugin.common.StandardMessageCodec
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
private object ProxyPigeonUtils {
|
||||
|
||||
fun wrapResult(result: Any?): List<Any?> {
|
||||
return listOf(result)
|
||||
}
|
||||
|
||||
fun wrapError(exception: Throwable): List<Any?> {
|
||||
return if (exception is FlutterError) {
|
||||
listOf(
|
||||
exception.code,
|
||||
exception.message,
|
||||
exception.details
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
exception.javaClass.simpleName,
|
||||
exception.toString(),
|
||||
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
||||
* @property code The error code.
|
||||
* @property message The error message.
|
||||
* @property details The error details. Must be a datatype supported by the api codec.
|
||||
*/
|
||||
class FlutterError (
|
||||
val code: String,
|
||||
override val message: String? = null,
|
||||
val details: Any? = null
|
||||
) : Throwable()
|
||||
|
||||
enum class ProxyType(val raw: Int) {
|
||||
OBFS4(0),
|
||||
MEEK_LITE(1),
|
||||
WEBTUNNEL(2),
|
||||
SNOWFLAKE(3);
|
||||
|
||||
companion object {
|
||||
fun ofRaw(raw: Int): ProxyType? {
|
||||
return values().firstOrNull { it.raw == raw }
|
||||
}
|
||||
}
|
||||
}
|
||||
private open class ProxyPigeonCodec : StandardMessageCodec() {
|
||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||
return when (type) {
|
||||
129.toByte() -> {
|
||||
return (readValue(buffer) as Long?)?.let {
|
||||
ProxyType.ofRaw(it.toInt())
|
||||
}
|
||||
}
|
||||
else -> super.readValueOfType(type, buffer)
|
||||
}
|
||||
}
|
||||
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||
when (value) {
|
||||
is ProxyType -> {
|
||||
stream.write(129)
|
||||
writeValue(stream, value.raw.toLong())
|
||||
}
|
||||
else -> super.writeValue(stream, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface IPtProxyController {
|
||||
fun start(proxyType: ProxyType, proxy: String): Long
|
||||
fun stop(proxyType: ProxyType)
|
||||
|
||||
companion object {
|
||||
/** The codec used by IPtProxyController. */
|
||||
val codec: MessageCodec<Any?> by lazy {
|
||||
ProxyPigeonCodec()
|
||||
}
|
||||
/** Sets up an instance of `IPtProxyController` to handle messages through the `binaryMessenger`. */
|
||||
@JvmOverloads
|
||||
fun setUp(binaryMessenger: BinaryMessenger, api: IPtProxyController?, messageChannelSuffix: String = "") {
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.start$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val proxyTypeArg = args[0] as ProxyType
|
||||
val proxyArg = args[1] as String
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.start(proxyTypeArg, proxyArg))
|
||||
} catch (exception: Throwable) {
|
||||
ProxyPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.stop$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val proxyTypeArg = args[0] as ProxyType
|
||||
val wrapped: List<Any?> = try {
|
||||
api.stop(proxyTypeArg)
|
||||
listOf(null)
|
||||
} catch (exception: Throwable) {
|
||||
ProxyPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2025 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
export 'src/data/models/moat.dart';
|
||||
export 'src/data/service/moat_service.dart';
|
||||
export 'src/pigeons/proxy.g.dart' show IPtProxyController, ProxyType;
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2025 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'moat.g.dart';
|
||||
|
||||
enum TransportType {
|
||||
obfs4('obfs4'),
|
||||
snowflake('snowflake'),
|
||||
meek('meek'),
|
||||
meekAzure('meek-azure'),
|
||||
webtunnel('webtunnel');
|
||||
|
||||
const TransportType(this.value);
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
static TransportType? fromString(String value) {
|
||||
for (final transport in TransportType.values) {
|
||||
if (transport.value == value) return transport;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class SettingsRequest {
|
||||
final String? country;
|
||||
@JsonKey(toJson: _transportsToJson, fromJson: _transportsFromJson)
|
||||
final List<TransportType> transports;
|
||||
|
||||
const SettingsRequest({
|
||||
this.country,
|
||||
this.transports = const [
|
||||
TransportType.obfs4,
|
||||
TransportType.snowflake,
|
||||
TransportType.webtunnel,
|
||||
],
|
||||
});
|
||||
|
||||
static List<String> _transportsToJson(List<TransportType> transports) =>
|
||||
transports.map((t) => t.value).toList();
|
||||
|
||||
static List<TransportType> _transportsFromJson(List<dynamic> json) => json
|
||||
.cast<String>()
|
||||
.map((s) => TransportType.fromString(s))
|
||||
.where((t) => t != null)
|
||||
.cast<TransportType>()
|
||||
.toList();
|
||||
|
||||
factory SettingsRequest.fromJson(Map<String, dynamic> json) =>
|
||||
_$SettingsRequestFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SettingsRequestToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class SettingsResponse {
|
||||
final List<Setting>? settings;
|
||||
final String? country;
|
||||
final List<MoatError>? errors;
|
||||
|
||||
const SettingsResponse({this.settings, this.country, this.errors});
|
||||
|
||||
factory SettingsResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$SettingsResponseFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SettingsResponseToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Setting {
|
||||
@JsonKey(name: 'bridges')
|
||||
final Bridge bridge;
|
||||
|
||||
const Setting({required this.bridge});
|
||||
|
||||
factory Setting.fromJson(Map<String, dynamic> json) =>
|
||||
_$SettingFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SettingToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Bridge {
|
||||
@JsonKey(toJson: _transportToJson, fromJson: _transportFromJson)
|
||||
final TransportType type;
|
||||
final String source;
|
||||
@JsonKey(name: 'bridge_strings')
|
||||
final List<String>? bridges;
|
||||
|
||||
const Bridge({required this.type, required this.source, this.bridges});
|
||||
|
||||
static String _transportToJson(TransportType transport) => transport.value;
|
||||
|
||||
static TransportType _transportFromJson(dynamic json) =>
|
||||
TransportType.fromString(json as String)!;
|
||||
|
||||
factory Bridge.fromJson(Map<String, dynamic> json) => _$BridgeFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$BridgeToJson(this);
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class MoatError implements Exception {
|
||||
final String? id;
|
||||
final String? type;
|
||||
final String? version;
|
||||
final int? code;
|
||||
final String? status;
|
||||
final String? detail;
|
||||
|
||||
const MoatError({
|
||||
this.id,
|
||||
this.type,
|
||||
this.version,
|
||||
this.code,
|
||||
this.status,
|
||||
this.detail,
|
||||
});
|
||||
|
||||
factory MoatError.fromJson(Map<String, dynamic> json) =>
|
||||
_$MoatErrorFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$MoatErrorToJson(this);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (detail != null && detail!.isNotEmpty) {
|
||||
return detail!;
|
||||
}
|
||||
return '$code $status';
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class BuiltInBridges {
|
||||
final List<String> meek;
|
||||
|
||||
@JsonKey(name: 'meek-azure')
|
||||
final List<String> meekAzure;
|
||||
|
||||
final List<String> obfs4;
|
||||
final List<String> snowflake;
|
||||
|
||||
const BuiltInBridges({
|
||||
required this.meek,
|
||||
required this.meekAzure,
|
||||
required this.obfs4,
|
||||
required this.snowflake,
|
||||
});
|
||||
|
||||
factory BuiltInBridges.fromJson(Map<String, dynamic> json) =>
|
||||
_$BuiltInBridgesFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$BuiltInBridgesToJson(this);
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'moat.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
SettingsRequest _$SettingsRequestFromJson(Map<String, dynamic> json) =>
|
||||
SettingsRequest(
|
||||
country: json['country'] as String?,
|
||||
transports: json['transports'] == null
|
||||
? const [
|
||||
TransportType.obfs4,
|
||||
TransportType.snowflake,
|
||||
TransportType.webtunnel,
|
||||
]
|
||||
: SettingsRequest._transportsFromJson(json['transports'] as List),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SettingsRequestToJson(SettingsRequest instance) =>
|
||||
<String, dynamic>{
|
||||
'country': instance.country,
|
||||
'transports': SettingsRequest._transportsToJson(instance.transports),
|
||||
};
|
||||
|
||||
SettingsResponse _$SettingsResponseFromJson(Map<String, dynamic> json) =>
|
||||
SettingsResponse(
|
||||
settings: (json['settings'] as List<dynamic>?)
|
||||
?.map((e) => Setting.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
country: json['country'] as String?,
|
||||
errors: (json['errors'] as List<dynamic>?)
|
||||
?.map((e) => MoatError.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SettingsResponseToJson(SettingsResponse instance) =>
|
||||
<String, dynamic>{
|
||||
'settings': instance.settings,
|
||||
'country': instance.country,
|
||||
'errors': instance.errors,
|
||||
};
|
||||
|
||||
Setting _$SettingFromJson(Map<String, dynamic> json) =>
|
||||
Setting(bridge: Bridge.fromJson(json['bridges'] as Map<String, dynamic>));
|
||||
|
||||
Map<String, dynamic> _$SettingToJson(Setting instance) => <String, dynamic>{
|
||||
'bridges': instance.bridge,
|
||||
};
|
||||
|
||||
Bridge _$BridgeFromJson(Map<String, dynamic> json) => Bridge(
|
||||
type: Bridge._transportFromJson(json['type']),
|
||||
source: json['source'] as String,
|
||||
bridges: (json['bridge_strings'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$BridgeToJson(Bridge instance) => <String, dynamic>{
|
||||
'type': Bridge._transportToJson(instance.type),
|
||||
'source': instance.source,
|
||||
'bridge_strings': instance.bridges,
|
||||
};
|
||||
|
||||
MoatError _$MoatErrorFromJson(Map<String, dynamic> json) => MoatError(
|
||||
id: json['id'] as String?,
|
||||
type: json['type'] as String?,
|
||||
version: json['version'] as String?,
|
||||
code: (json['code'] as num?)?.toInt(),
|
||||
status: json['status'] as String?,
|
||||
detail: json['detail'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$MoatErrorToJson(MoatError instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'type': instance.type,
|
||||
'version': instance.version,
|
||||
'code': instance.code,
|
||||
'status': instance.status,
|
||||
'detail': instance.detail,
|
||||
};
|
||||
|
||||
BuiltInBridges _$BuiltInBridgesFromJson(Map<String, dynamic> json) =>
|
||||
BuiltInBridges(
|
||||
meek: (json['meek'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
meekAzure: (json['meek-azure'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
obfs4: (json['obfs4'] as List<dynamic>).map((e) => e as String).toList(),
|
||||
snowflake: (json['snowflake'] as List<dynamic>)
|
||||
.map((e) => e as String)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$BuiltInBridgesToJson(BuiltInBridges instance) =>
|
||||
<String, dynamic>{
|
||||
'meek': instance.meek,
|
||||
'meek-azure': instance.meekAzure,
|
||||
'obfs4': instance.obfs4,
|
||||
'snowflake': instance.snowflake,
|
||||
};
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2025 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:pluggable_transports_proxy/src/data/models/moat.dart';
|
||||
|
||||
class MoatApi {
|
||||
static const String _baseUrl =
|
||||
'https://bridges.torproject.org/moat/circumvention/';
|
||||
|
||||
final http.Client _client;
|
||||
|
||||
MoatApi(this._client);
|
||||
|
||||
Future<SettingsResponse> settings([SettingsRequest? request]) async {
|
||||
final response = await _post(
|
||||
'settings',
|
||||
request ?? const SettingsRequest(),
|
||||
);
|
||||
return SettingsResponse.fromJson(
|
||||
jsonDecode(response.body) as Map<String, dynamic>,
|
||||
);
|
||||
}
|
||||
|
||||
Future<SettingsResponse> defaults([SettingsRequest? request]) async {
|
||||
final response = await _post(
|
||||
'defaults',
|
||||
request ?? const SettingsRequest(),
|
||||
);
|
||||
return SettingsResponse.fromJson(
|
||||
jsonDecode(response.body) as Map<String, dynamic>,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, SettingsResponse>> map() async {
|
||||
final response = await _get('map');
|
||||
final Map<String, dynamic> data =
|
||||
jsonDecode(response.body) as Map<String, dynamic>;
|
||||
return data.map(
|
||||
(key, value) => MapEntry(
|
||||
key,
|
||||
SettingsResponse.fromJson(value as Map<String, dynamic>),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<BuiltInBridges> builtin() async {
|
||||
final response = await _get('builtin');
|
||||
return BuiltInBridges.fromJson(
|
||||
jsonDecode(response.body) as Map<String, dynamic>,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<String>> countries() async {
|
||||
final response = await _get('countries');
|
||||
return List<String>.from(jsonDecode(response.body) as List);
|
||||
}
|
||||
|
||||
Future<http.Response> _get(String endpoint) async {
|
||||
final uri = Uri.parse('$_baseUrl$endpoint');
|
||||
return await _client
|
||||
.get(uri, headers: _headers)
|
||||
.timeout(const Duration(seconds: 15));
|
||||
}
|
||||
|
||||
Future<http.Response> _post(String endpoint, Object body) async {
|
||||
final uri = Uri.parse('$_baseUrl$endpoint');
|
||||
return await _client
|
||||
.post(uri, headers: _headers, body: jsonEncode(body))
|
||||
.timeout(const Duration(seconds: 15));
|
||||
}
|
||||
|
||||
Map<String, String> get _headers => {
|
||||
'Content-Type': 'application/vnd.api+json',
|
||||
'Accept': 'application/vnd.api+json',
|
||||
};
|
||||
|
||||
void dispose() {
|
||||
_client.close();
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2025 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:pluggable_transports_proxy/pluggable_transports_proxy.dart';
|
||||
import 'package:pluggable_transports_proxy/src/data/service/moat_api.dart';
|
||||
import 'package:socks5_proxy/socks_client.dart';
|
||||
|
||||
const String _meekParameters =
|
||||
'url=https://1723079976.rsc.cdn77.org;front=www.phpmyadmin.net';
|
||||
|
||||
const defaultBridges = [TransportType.obfs4, TransportType.snowflake];
|
||||
|
||||
/// Manages MOAT API connections with persistent proxy setup.
|
||||
class MoatService {
|
||||
MoatApi? _api;
|
||||
|
||||
/// Initializes the MeekLite proxy and MOAT API connection.
|
||||
/// Must be called before using other methods.
|
||||
Future<void> initialize() async {
|
||||
if (_api != null) return;
|
||||
|
||||
final port = await IPtProxyController().start(ProxyType.meekLite, "");
|
||||
|
||||
final httpClient = HttpClient();
|
||||
SocksTCPClient.assignToHttpClient(httpClient, [
|
||||
ProxySettings(
|
||||
InternetAddress.loopbackIPv4,
|
||||
port,
|
||||
username: _meekParameters,
|
||||
password: '\u0000',
|
||||
),
|
||||
]);
|
||||
|
||||
_api = MoatApi(IOClient(httpClient));
|
||||
}
|
||||
|
||||
/// Disposes the API connection and stops the MeekLite proxy.
|
||||
/// Should be called when done using the client.
|
||||
Future<void> dispose() async {
|
||||
if (_api == null) return;
|
||||
|
||||
_api!.dispose();
|
||||
await IPtProxyController().stop(ProxyType.meekLite);
|
||||
_api = null;
|
||||
}
|
||||
|
||||
/// Ensures the client is initialized before API calls.
|
||||
void _ensureInitialized() {
|
||||
if (_api == null) {
|
||||
throw StateError(
|
||||
'MoatClient must be initialized before use. Call initialize() first.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles MOAT API errors and determines if PT is required.
|
||||
bool _handleMoatErrors(List<MoatError>? errors) {
|
||||
if (errors == null || errors.isEmpty) return false;
|
||||
|
||||
final error = errors.first;
|
||||
if (error.code == 404 || error.code == 406) {
|
||||
// 404: Needs transport, but not the available ones
|
||||
// 406: No country from IP address
|
||||
return true;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
/// Converts BuiltInBridges to Settings for requested transports.
|
||||
static List<Setting> convertBuiltinToSettings(
|
||||
BuiltInBridges builtinBridges, {
|
||||
List<TransportType> transports = defaultBridges,
|
||||
}) {
|
||||
final settings = <Setting>[];
|
||||
|
||||
for (final transport in transports) {
|
||||
final bridgeStrings = switch (transport) {
|
||||
TransportType.obfs4 => builtinBridges.obfs4,
|
||||
TransportType.snowflake => builtinBridges.snowflake,
|
||||
TransportType.meek => builtinBridges.meek,
|
||||
TransportType.meekAzure => builtinBridges.meekAzure,
|
||||
TransportType.webtunnel => <String>[], // Not available in builtin
|
||||
};
|
||||
|
||||
if (bridgeStrings.isNotEmpty) {
|
||||
settings.add(
|
||||
Setting(
|
||||
bridge: Bridge(
|
||||
type: transport,
|
||||
source: 'builtin',
|
||||
bridges: bridgeStrings,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// Gets built-in bridges from the MOAT service endpoint.
|
||||
Future<BuiltInBridges> getBuiltinBridges({
|
||||
List<TransportType> transports = defaultBridges,
|
||||
}) async {
|
||||
_ensureInitialized();
|
||||
|
||||
final builtinBridges = await _api!.builtin();
|
||||
return builtinBridges;
|
||||
}
|
||||
|
||||
/// Tries to automatically configure Pluggable Transports.
|
||||
Future<List<Setting>?> autoConf({
|
||||
String? country,
|
||||
bool cannotConnectWithoutPt = false,
|
||||
List<TransportType> transports = defaultBridges,
|
||||
}) async {
|
||||
_ensureInitialized();
|
||||
|
||||
bool localCannotConnectWithoutPt = cannotConnectWithoutPt;
|
||||
final request = SettingsRequest(country: country, transports: transports);
|
||||
|
||||
var response = await _api!.settings(request);
|
||||
|
||||
if (_handleMoatErrors(response.errors)) {
|
||||
localCannotConnectWithoutPt = true;
|
||||
}
|
||||
|
||||
final hasSettings = response.settings?.isNotEmpty ?? false;
|
||||
if (!hasSettings && !localCannotConnectWithoutPt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hasSettings) {
|
||||
return response.settings;
|
||||
}
|
||||
|
||||
response = await _api!.defaults(SettingsRequest(transports: transports));
|
||||
return response.settings;
|
||||
}
|
||||
|
||||
Future<List<Setting>?> getDefaultBridges({
|
||||
List<TransportType> transports = defaultBridges,
|
||||
}) async {
|
||||
_ensureInitialized();
|
||||
|
||||
final response = await _api!.defaults(
|
||||
SettingsRequest(transports: transports),
|
||||
);
|
||||
|
||||
return response.settings;
|
||||
}
|
||||
|
||||
/// Gets the list of supported countries from the MOAT service.
|
||||
Future<List<String>> getCountries() async {
|
||||
_ensureInitialized();
|
||||
return await _api!.countries();
|
||||
}
|
||||
|
||||
/// Gets the country-to-settings map from the MOAT service.
|
||||
Future<Map<String, SettingsResponse>> getMap() async {
|
||||
_ensureInitialized();
|
||||
return await _api!.map();
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
// Autogenerated from Pigeon (v26.1.5), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, omit_obvious_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
|
||||
|
||||
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
PlatformException _createConnectionError(String channelName) {
|
||||
return PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel: "$channelName".',
|
||||
);
|
||||
}
|
||||
|
||||
enum ProxyType {
|
||||
obfs4,
|
||||
meekLite,
|
||||
webtunnel,
|
||||
snowflake,
|
||||
}
|
||||
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
void writeValue(WriteBuffer buffer, Object? value) {
|
||||
if (value is int) {
|
||||
buffer.putUint8(4);
|
||||
buffer.putInt64(value);
|
||||
} else if (value is ProxyType) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.index);
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 129:
|
||||
final value = readValue(buffer) as int?;
|
||||
return value == null ? null : ProxyType.values[value];
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IPtProxyController {
|
||||
/// Constructor for [IPtProxyController]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
IPtProxyController({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
|
||||
final String pigeonVar_messageChannelSuffix;
|
||||
|
||||
Future<int> start(ProxyType proxyType, String proxy) async {
|
||||
final pigeonVar_channelName = 'dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.start$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType, proxy]);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else if (pigeonVar_replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (pigeonVar_replyList[0] as int?)!;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stop(ProxyType proxyType) async {
|
||||
final pigeonVar_channelName = 'dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.stop$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[proxyType]);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2024-2025 Fabian Freund.
|
||||
*
|
||||
* This file is part of WebLibre
|
||||
* (see https://weblibre.eu).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import 'package:pigeon/pigeon.dart';
|
||||
|
||||
enum ProxyType { obfs4, meekLite, webtunnel, snowflake }
|
||||
|
||||
@ConfigurePigeon(
|
||||
PigeonOptions(
|
||||
dartOut: 'lib/src/pigeons/proxy.g.dart',
|
||||
dartOptions: DartOptions(),
|
||||
kotlinOut:
|
||||
'android/src/main/kotlin/eu/weblibre/pluggable_transports_proxy/pigeons/Proxy.g.kt',
|
||||
kotlinOptions: KotlinOptions(
|
||||
package: 'eu.weblibre.pluggable_transports_proxy.pigeons',
|
||||
),
|
||||
dartPackageName: 'pluggable_transports_proxy',
|
||||
),
|
||||
)
|
||||
@HostApi()
|
||||
abstract class IPtProxyController {
|
||||
int start(ProxyType proxyType, String proxy);
|
||||
void stop(ProxyType proxyType);
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
name: pluggable_transports_proxy
|
||||
description: "A new Flutter plugin project."
|
||||
version: 0.0.1
|
||||
publish_to: 'none'
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.0
|
||||
flutter: '>=3.3.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
http: ^1.6.0
|
||||
json_annotation: ^4.9.0
|
||||
socks5_proxy: ^2.1.1
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.10.4
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
json_serializable: ^6.11.2
|
||||
lint: ^2.8.0
|
||||
pigeon: ^26.1.5
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
|
||||
# which should be registered in the plugin registry. This is required for
|
||||
# using method channels.
|
||||
# The Android 'package' specifies package in which the registered class is.
|
||||
# This is required for using method channels on Android.
|
||||
# The 'ffiPlugin' specifies that native code should be built and bundled.
|
||||
# This is required for using `dart:ffi`.
|
||||
# All these are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: eu.weblibre.pluggable_transports_proxy
|
||||
pluginClass: PluggableTransportsProxyPlugin
|
||||
|
||||
# To add assets to your plugin package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/to/resolution-aware-images
|
||||
|
||||
# To add custom fonts to your plugin package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/to/font-from-package
|
||||
@@ -1,38 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
rust/target/.rustc_info.json
|
||||
rust/target/debug
|
||||
rust/target/flycheck0
|
||||
@@ -1,42 +0,0 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1"
|
||||
channel: "stable"
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
- platform: android
|
||||
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
- platform: ios
|
||||
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
- platform: linux
|
||||
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
- platform: macos
|
||||
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
- platform: windows
|
||||
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
@@ -1,32 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: tor
|
||||
Upstream-Contact: Igor Cota <igor@foundationdevices.com>
|
||||
Source: https://github.com/Foundation-Devices/tor
|
||||
|
||||
Files: cargokit/*
|
||||
Copyright: 2022 Matej Knopp
|
||||
License: MIT
|
||||
|
||||
Files: lib/generated*
|
||||
Copyright: 2024 Foundation Devices Inc.
|
||||
License: MIT
|
||||
|
||||
Files: example/linux/flutter/generated*
|
||||
Copyright: 2024 Foundation Devices Inc.
|
||||
License: MIT
|
||||
|
||||
Files: example/macos/Flutter/Generated*
|
||||
Copyright: 2024 Foundation Devices Inc.
|
||||
License: MIT
|
||||
|
||||
Files: example/windows/flutter/generated*
|
||||
Copyright: 2024 Foundation Devices Inc.
|
||||
License: MIT
|
||||
|
||||
Files: rust/target/*
|
||||
Copyright: 2024 Foundation Devices Inc.
|
||||
License: MIT
|
||||
|
||||
Files: CHANGELOG.md
|
||||
Copyright: 2024 Foundation Devices Inc.
|
||||
License: MIT
|
||||
@@ -1,41 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
## 0.0.9
|
||||
|
||||
* Bumped arti to version 1.4.3
|
||||
|
||||
## 0.0.8
|
||||
|
||||
* Bumped arti to version 1.2.7
|
||||
|
||||
## 0.0.7
|
||||
|
||||
* Relicensed to MIT
|
||||
|
||||
## 0.0.6
|
||||
|
||||
* Pinned 'time' to a specific version
|
||||
|
||||
## 0.0.5
|
||||
|
||||
* Added rust-toolchain file
|
||||
|
||||
## 0.0.4
|
||||
|
||||
* Bumped arti to version 1.2.4
|
||||
|
||||
## 0.0.3
|
||||
|
||||
* Added functions to stop and restart the proxy.
|
||||
|
||||
## 0.0.2
|
||||
|
||||
* Fixed the Windows build.
|
||||
|
||||
## 0.0.1
|
||||
|
||||
* Initial release.
|
||||
@@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Foundation Devices Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Foundation Devices Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,41 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2022-2023 Foundation Devices Inc.
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
# tor
|
||||
|
||||
[foundation-Devices/tor](https://github.com/Foundation-Devices/tor) is a multi-platform Flutter plugin for managing a Tor proxy. Based on [arti](https://gitlab.torproject.org/tpo/core/arti).
|
||||
|
||||
## Getting started
|
||||
|
||||
### [Install rust](https://www.rust-lang.org/tools/install)
|
||||
|
||||
Use `rustup`, not `homebrew`.
|
||||
|
||||
### Install cargo ndk
|
||||
|
||||
```sh
|
||||
cargo install cargo-ndk
|
||||
```
|
||||
|
||||
### Cargokit
|
||||
|
||||
[Cargokit](https://github.com/irondash/cargokit) handles building, just `flutter run` it or run it in Android Studio or VS Code (untested).
|
||||
|
||||
To update Cargokit in the future, use:
|
||||
```sh
|
||||
git subtree pull --prefix cargokit https://github.com/irondash/cargokit.git main --squash
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To (re)generate Dart bindings run `just generate`
|
||||
|
||||
## Example app
|
||||
|
||||
`flutter run` in `example` to run the example app
|
||||
|
||||
See `example/lib/main.dart` for usage.
|
||||
@@ -1,40 +0,0 @@
|
||||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lint/strict.yaml
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
linter:
|
||||
rules:
|
||||
unawaited_futures: true
|
||||
discarded_futures: true
|
||||
collection_methods_unrelated_type: true
|
||||
|
||||
analyzer:
|
||||
errors:
|
||||
overridden_fields: ignore
|
||||
plugins:
|
||||
- custom_lint
|
||||
exclude:
|
||||
- "**.drift"
|
||||
- "**.g.dart"
|
||||
- "**.swagger.dart"
|
||||
- "**.freezed.dart"
|
||||
- "**.chopper.dart"
|
||||
- "lib/src/generated_bindings.dart"
|
||||
- "cargokit/"
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -1,13 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
@@ -1,73 +0,0 @@
|
||||
group = "com.foundationdevices.tor"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = "2.1.20"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.13.2")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.android.library"
|
||||
apply plugin: "kotlin-android"
|
||||
|
||||
android {
|
||||
namespace = "com.foundationdevices.tor"
|
||||
|
||||
compileSdk = 36
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += "src/main/kotlin"
|
||||
test.java.srcDirs += "src/test/kotlin"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.mockito:mockito-core:5.0.0")
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "../cargokit/gradle/plugin.gradle"
|
||||
|
||||
cargokit {
|
||||
manifestDir = "../rust"
|
||||
libname = "tor"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
rootProject.name = 'tor'
|
||||
@@ -1,9 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.foundationdevices.tor">
|
||||
</manifest>
|
||||
@@ -1,30 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: Check and Lint
|
||||
|
||||
jobs:
|
||||
Flutter:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # 4.1.0
|
||||
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # 2.16.0
|
||||
- name: Pub Get
|
||||
run: dart pub get --no-precompile
|
||||
working-directory: build_tool
|
||||
- name: Dart Format
|
||||
run: dart format . --output=none --set-exit-if-changed
|
||||
working-directory: build_tool
|
||||
- name: Analyze
|
||||
run: dart analyze
|
||||
working-directory: build_tool
|
||||
- name: Test
|
||||
run: flutter test
|
||||
working-directory: build_tool
|
||||
@@ -1,90 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
name: Test Example Plugin
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macOS-latest
|
||||
- windows-latest
|
||||
build_mode:
|
||||
- debug
|
||||
- profile
|
||||
- release
|
||||
env:
|
||||
EXAMPLE_DIR: "a b/hello_rust_ffi_plugin/example"
|
||||
CARGOKIT_VERBOSE: 1
|
||||
steps:
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
|
||||
id: extract_branch
|
||||
- name: Setup Repository
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir "a b" # Space is intentional
|
||||
cd "a b"
|
||||
git config --global user.email "you@example.com"
|
||||
git config --global user.name "Your Name"
|
||||
# "advanced" branch has extra iOS flavor and uses rust nightly for release builds
|
||||
git clone -b advanced https://github.com/irondash/hello_rust_ffi_plugin
|
||||
cd hello_rust_ffi_plugin
|
||||
git subtree pull --prefix cargokit https://github.com/${{ github.event.pull_request.head.repo.full_name || github.repository }} ${{ steps.extract_branch.outputs.branch }} --squash
|
||||
- uses: subosito/flutter-action@44ac965b96f18d999802d4b807e3256d5a3f9fa1 # 2.16.0
|
||||
with:
|
||||
channel: "stable"
|
||||
- name: Install GTK
|
||||
if: (matrix.os == 'ubuntu-latest')
|
||||
run: sudo apt-get update && sudo apt-get install libgtk-3-dev
|
||||
- name: Install ninja-build
|
||||
if: (matrix.os == 'ubuntu-latest')
|
||||
run: sudo apt-get update && sudo apt-get install ninja-build
|
||||
- name: Build Linux (${{ matrix.build_mode }})
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
shell: bash
|
||||
working-directory: ${{ env.EXAMPLE_DIR }}
|
||||
run: flutter build linux --${{ matrix.build_mode }} -v
|
||||
- name: Build macOS (${{ matrix.build_mode }})
|
||||
if: matrix.os == 'macos-latest'
|
||||
shell: bash
|
||||
working-directory: ${{ env.EXAMPLE_DIR }}
|
||||
run: flutter build macos --${{ matrix.build_mode }} -v
|
||||
- name: Build iOS (${{ matrix.build_mode }})
|
||||
if: matrix.os == 'macos-latest'
|
||||
shell: bash
|
||||
working-directory: ${{ env.EXAMPLE_DIR }}
|
||||
run: flutter build ios --${{ matrix.build_mode }} --no-codesign -v
|
||||
- name: Build iOS (${{ matrix.build_mode }}) - flavor1
|
||||
if: matrix.os == 'macos-latest'
|
||||
shell: bash
|
||||
working-directory: ${{ env.EXAMPLE_DIR }}
|
||||
run: flutter build ios --flavor flavor1 --${{ matrix.build_mode }} --no-codesign -v
|
||||
- name: Build Windows (${{ matrix.build_mode }})
|
||||
if: matrix.os == 'windows-latest'
|
||||
shell: bash
|
||||
working-directory: ${{ env.EXAMPLE_DIR }}
|
||||
run: flutter build windows --${{ matrix.build_mode }} -v
|
||||
- name: Build Android (${{ matrix.build_mode }})
|
||||
shell: bash
|
||||
working-directory: ${{ env.EXAMPLE_DIR }}
|
||||
run: |
|
||||
if [[ $(sysctl hw.optional.arm64) == *"hw.optional.arm64: 1"* ]]; then
|
||||
export JAVA_HOME=$JAVA_HOME_17_arm64
|
||||
else
|
||||
export JAVA_HOME=$JAVA_HOME_17_X64
|
||||
fi
|
||||
flutter build apk --${{ matrix.build_mode }} -v
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
target
|
||||
.dart_tool
|
||||
*.iml
|
||||
!pubspec.lock
|
||||
@@ -1,39 +0,0 @@
|
||||
Copyright 2022 Matej Knopp
|
||||
|
||||
================================================================================
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
|
||||
APACHE LICENSE, VERSION 2.0
|
||||
|
||||
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.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
Experimental repository to provide glue for seamlessly integrating cargo build
|
||||
with flutter plugins and packages.
|
||||
|
||||
See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/
|
||||
for a tutorial on how to use Cargokit.
|
||||
|
||||
Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
set -e
|
||||
|
||||
BASEDIR=$(dirname "$0")
|
||||
|
||||
# Workaround for https://github.com/dart-lang/pub/issues/4010
|
||||
BASEDIR=$(cd "$BASEDIR" ; pwd -P)
|
||||
|
||||
# Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project
|
||||
NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"`
|
||||
|
||||
export PATH=${NEW_PATH%?} # remove trailing :
|
||||
|
||||
env
|
||||
|
||||
# Platform name (macosx, iphoneos, iphonesimulator)
|
||||
export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME
|
||||
|
||||
# Arctive architectures (arm64, armv7, x86_64), space separated.
|
||||
export CARGOKIT_DARWIN_ARCHS=$ARCHS
|
||||
|
||||
# Current build configuration (Debug, Release)
|
||||
export CARGOKIT_CONFIGURATION=$CONFIGURATION
|
||||
|
||||
# Path to directory containing Cargo.toml.
|
||||
export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1
|
||||
|
||||
# Temporary directory for build artifacts.
|
||||
export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR
|
||||
|
||||
# Output directory for final artifacts.
|
||||
export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME
|
||||
|
||||
# Directory to store built tool artifacts.
|
||||
export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool
|
||||
|
||||
# Directory inside root project. Not necessarily the top level directory of root project.
|
||||
export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT
|
||||
|
||||
FLUTTER_EXPORT_BUILD_ENVIRONMENT=(
|
||||
"$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS
|
||||
"$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS
|
||||
)
|
||||
|
||||
for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}"
|
||||
do
|
||||
if [[ -f "$path" ]]; then
|
||||
source "$path"
|
||||
fi
|
||||
done
|
||||
|
||||
sh "$BASEDIR/run_build_tool.sh" build-pod "$@"
|
||||
|
||||
# Make a symlink from built framework to phony file, which will be used as input to
|
||||
# build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate
|
||||
# attribute on custom build phase)
|
||||
ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony"
|
||||
ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out"
|
||||
@@ -1,8 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
A sample command-line application with an entrypoint in `bin/`, library code
|
||||
in `lib/`, and example unit test in `test/`.
|
||||
@@ -1,35 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# This file configures the static analysis results for your project (errors,
|
||||
# warnings, and lints).
|
||||
#
|
||||
# This enables the 'recommended' set of lints from `package:lints`.
|
||||
# This set helps identify many issues that may lead to problems when running
|
||||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||
# style and format.
|
||||
#
|
||||
# If you want a smaller set of lints you can change this to specify
|
||||
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||
# (the recommended set includes the core lints).
|
||||
# The core lints are also what is used by pub.dev for scoring packages.
|
||||
|
||||
include: package:lints/recommended.yaml
|
||||
|
||||
# Uncomment the following section to specify additional rules.
|
||||
|
||||
linter:
|
||||
rules:
|
||||
- prefer_relative_imports
|
||||
- directives_ordering
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
||||
|
||||
# For more information about the core and recommended set of lints, see
|
||||
# https://dart.dev/go/core-lints
|
||||
|
||||
# For additional information about configuring this file, see
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -1,9 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'package:build_tool/build_tool.dart' as build_tool;
|
||||
|
||||
void main(List<String> arguments) {
|
||||
build_tool.runMain(arguments);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'src/build_tool.dart' as build_tool;
|
||||
|
||||
Future<void> runMain(List<String> args) async {
|
||||
return build_tool.runMain(args);
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:version/version.dart';
|
||||
|
||||
import 'target.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class AndroidEnvironment {
|
||||
AndroidEnvironment({
|
||||
required this.sdkPath,
|
||||
required this.ndkVersion,
|
||||
required this.minSdkVersion,
|
||||
required this.targetTempDir,
|
||||
required this.target,
|
||||
});
|
||||
|
||||
static void clangLinkerWrapper(List<String> args) {
|
||||
final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG'];
|
||||
if (clang == null) {
|
||||
throw Exception(
|
||||
"cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var");
|
||||
}
|
||||
final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET'];
|
||||
if (target == null) {
|
||||
throw Exception(
|
||||
"cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var");
|
||||
}
|
||||
|
||||
runCommand(clang, [
|
||||
target,
|
||||
...args,
|
||||
]);
|
||||
}
|
||||
|
||||
/// Full path to Android SDK.
|
||||
final String sdkPath;
|
||||
|
||||
/// Full version of Android NDK.
|
||||
final String ndkVersion;
|
||||
|
||||
/// Minimum supported SDK version.
|
||||
final int minSdkVersion;
|
||||
|
||||
/// Target directory for build artifacts.
|
||||
final String targetTempDir;
|
||||
|
||||
/// Target being built.
|
||||
final Target target;
|
||||
|
||||
bool ndkIsInstalled() {
|
||||
final ndkPath = path.join(sdkPath, 'ndk', ndkVersion);
|
||||
final ndkPackageXml = File(path.join(ndkPath, 'package.xml'));
|
||||
return ndkPackageXml.existsSync();
|
||||
}
|
||||
|
||||
void installNdk({
|
||||
required String javaHome,
|
||||
}) {
|
||||
final sdkManagerExtension = Platform.isWindows ? '.bat' : '';
|
||||
final sdkManager = path.join(
|
||||
sdkPath,
|
||||
'cmdline-tools',
|
||||
'latest',
|
||||
'bin',
|
||||
'sdkmanager$sdkManagerExtension',
|
||||
);
|
||||
|
||||
log.info('Installing NDK $ndkVersion');
|
||||
runCommand(sdkManager, [
|
||||
'--install',
|
||||
'ndk;$ndkVersion',
|
||||
], environment: {
|
||||
'JAVA_HOME': javaHome,
|
||||
});
|
||||
}
|
||||
|
||||
Future<Map<String, String>> buildEnvironment() async {
|
||||
final hostArch = Platform.isMacOS
|
||||
? "darwin-x86_64"
|
||||
: (Platform.isLinux ? "linux-x86_64" : "windows-x86_64");
|
||||
|
||||
final ndkPath = path.join(sdkPath, 'ndk', ndkVersion);
|
||||
final toolchainPath = path.join(
|
||||
ndkPath,
|
||||
'toolchains',
|
||||
'llvm',
|
||||
'prebuilt',
|
||||
hostArch,
|
||||
'bin',
|
||||
);
|
||||
|
||||
final minSdkVersion =
|
||||
math.max(target.androidMinSdkVersion!, this.minSdkVersion);
|
||||
|
||||
final exe = Platform.isWindows ? '.exe' : '';
|
||||
|
||||
final arKey = 'AR_${target.rust}';
|
||||
final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe']
|
||||
.map((e) => path.join(toolchainPath, e))
|
||||
.firstWhereOrNull((element) => File(element).existsSync());
|
||||
if (arValue == null) {
|
||||
throw Exception('Failed to find ar for $target in $toolchainPath');
|
||||
}
|
||||
|
||||
final targetArg = '--target=${target.rust}$minSdkVersion';
|
||||
|
||||
final ccKey = 'CC_${target.rust}';
|
||||
final ccValue = path.join(toolchainPath, 'clang$exe');
|
||||
final cfFlagsKey = 'CFLAGS_${target.rust}';
|
||||
final cFlagsValue = targetArg;
|
||||
|
||||
final cxxKey = 'CXX_${target.rust}';
|
||||
final cxxValue = path.join(toolchainPath, 'clang++$exe');
|
||||
final cxxFlagsKey = 'CXXFLAGS_${target.rust}';
|
||||
final cxxFlagsValue = targetArg;
|
||||
|
||||
final linkerKey =
|
||||
'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase();
|
||||
|
||||
final ranlibKey = 'RANLIB_${target.rust}';
|
||||
final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe');
|
||||
|
||||
final ndkVersionParsed = Version.parse(ndkVersion);
|
||||
final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS';
|
||||
final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed);
|
||||
|
||||
final runRustTool =
|
||||
Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh';
|
||||
|
||||
final packagePath = (await Isolate.resolvePackageUri(
|
||||
Uri.parse('package:build_tool/buildtool.dart')))!
|
||||
.toFilePath();
|
||||
final selfPath = path.canonicalize(path.join(
|
||||
packagePath,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
runRustTool,
|
||||
));
|
||||
|
||||
// Make sure that run_build_tool is working properly even initially launched directly
|
||||
// through dart run.
|
||||
final toolTempDir =
|
||||
Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir;
|
||||
|
||||
return {
|
||||
arKey: arValue,
|
||||
ccKey: ccValue,
|
||||
cfFlagsKey: cFlagsValue,
|
||||
cxxKey: cxxValue,
|
||||
cxxFlagsKey: cxxFlagsValue,
|
||||
ranlibKey: ranlibValue,
|
||||
rustFlagsKey: rustFlagsValue,
|
||||
linkerKey: selfPath,
|
||||
// Recognized by main() so we know when we're acting as a wrapper
|
||||
'_CARGOKIT_NDK_LINK_TARGET': targetArg,
|
||||
'_CARGOKIT_NDK_LINK_CLANG': ccValue,
|
||||
'CARGOKIT_TOOL_TEMP_DIR': toolTempDir,
|
||||
};
|
||||
}
|
||||
|
||||
// Workaround for libgcc missing in NDK23, inspired by cargo-ndk
|
||||
String _libGccWorkaround(String buildDir, Version ndkVersion) {
|
||||
final workaroundDir = path.join(
|
||||
buildDir,
|
||||
'cargokit',
|
||||
'libgcc_workaround',
|
||||
'${ndkVersion.major}',
|
||||
);
|
||||
Directory(workaroundDir).createSync(recursive: true);
|
||||
if (ndkVersion.major >= 23) {
|
||||
File(path.join(workaroundDir, 'libgcc.a'))
|
||||
.writeAsStringSync('INPUT(-lunwind)');
|
||||
} else {
|
||||
// Other way around, untested, forward libgcc.a from libunwind once Rust
|
||||
// gets updated for NDK23+.
|
||||
File(path.join(workaroundDir, 'libunwind.a'))
|
||||
.writeAsStringSync('INPUT(-lgcc)');
|
||||
}
|
||||
|
||||
var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? '';
|
||||
if (rustFlags.isNotEmpty) {
|
||||
rustFlags = '$rustFlags\x1f';
|
||||
}
|
||||
rustFlags = '$rustFlags-L\x1f$workaroundDir';
|
||||
return rustFlags;
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ed25519_edwards/ed25519_edwards.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'builder.dart';
|
||||
import 'crate_hash.dart';
|
||||
import 'options.dart';
|
||||
import 'precompile_binaries.dart';
|
||||
import 'rustup.dart';
|
||||
import 'target.dart';
|
||||
|
||||
class Artifact {
|
||||
/// File system location of the artifact.
|
||||
final String path;
|
||||
|
||||
/// Actual file name that the artifact should have in destination folder.
|
||||
final String finalFileName;
|
||||
|
||||
AritifactType get type {
|
||||
if (finalFileName.endsWith('.dll') ||
|
||||
finalFileName.endsWith('.dll.lib') ||
|
||||
finalFileName.endsWith('.pdb') ||
|
||||
finalFileName.endsWith('.so') ||
|
||||
finalFileName.endsWith('.dylib')) {
|
||||
return AritifactType.dylib;
|
||||
} else if (finalFileName.endsWith('.lib') || finalFileName.endsWith('.a')) {
|
||||
return AritifactType.staticlib;
|
||||
} else {
|
||||
throw Exception('Unknown artifact type for $finalFileName');
|
||||
}
|
||||
}
|
||||
|
||||
Artifact({
|
||||
required this.path,
|
||||
required this.finalFileName,
|
||||
});
|
||||
}
|
||||
|
||||
final _log = Logger('artifacts_provider');
|
||||
|
||||
class ArtifactProvider {
|
||||
ArtifactProvider({
|
||||
required this.environment,
|
||||
required this.userOptions,
|
||||
});
|
||||
|
||||
final BuildEnvironment environment;
|
||||
final CargokitUserOptions userOptions;
|
||||
|
||||
Future<Map<Target, List<Artifact>>> getArtifacts(List<Target> targets) async {
|
||||
final result = await _getPrecompiledArtifacts(targets);
|
||||
|
||||
final pendingTargets = List.of(targets);
|
||||
pendingTargets.removeWhere((element) => result.containsKey(element));
|
||||
|
||||
if (pendingTargets.isEmpty) {
|
||||
return result;
|
||||
}
|
||||
|
||||
final rustup = Rustup();
|
||||
for (final target in targets) {
|
||||
final builder = RustBuilder(target: target, environment: environment);
|
||||
builder.prepare(rustup);
|
||||
_log.info('Building ${environment.crateInfo.packageName} for $target');
|
||||
final targetDir = await builder.build();
|
||||
// For local build accept both static and dynamic libraries.
|
||||
final artifactNames = <String>{
|
||||
...getArtifactNames(
|
||||
target: target,
|
||||
libraryName: environment.crateInfo.packageName,
|
||||
aritifactType: AritifactType.dylib,
|
||||
remote: false,
|
||||
),
|
||||
...getArtifactNames(
|
||||
target: target,
|
||||
libraryName: environment.crateInfo.packageName,
|
||||
aritifactType: AritifactType.staticlib,
|
||||
remote: false,
|
||||
)
|
||||
};
|
||||
final artifacts = artifactNames
|
||||
.map((artifactName) => Artifact(
|
||||
path: path.join(targetDir, artifactName),
|
||||
finalFileName: artifactName,
|
||||
))
|
||||
.where((element) => File(element.path).existsSync())
|
||||
.toList();
|
||||
result[target] = artifacts;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<Map<Target, List<Artifact>>> _getPrecompiledArtifacts(
|
||||
List<Target> targets) async {
|
||||
if (userOptions.usePrecompiledBinaries == false) {
|
||||
_log.info('Precompiled binaries are disabled');
|
||||
return {};
|
||||
}
|
||||
if (environment.crateOptions.precompiledBinaries == null) {
|
||||
_log.fine('Precompiled binaries not enabled for this crate');
|
||||
return {};
|
||||
}
|
||||
|
||||
final start = Stopwatch()..start();
|
||||
final crateHash = CrateHash.compute(environment.manifestDir,
|
||||
tempStorage: environment.targetTempDir);
|
||||
_log.fine(
|
||||
'Computed crate hash $crateHash in ${start.elapsedMilliseconds}ms');
|
||||
|
||||
final downloadedArtifactsDir =
|
||||
path.join(environment.targetTempDir, 'precompiled', crateHash);
|
||||
Directory(downloadedArtifactsDir).createSync(recursive: true);
|
||||
|
||||
final res = <Target, List<Artifact>>{};
|
||||
|
||||
for (final target in targets) {
|
||||
final requiredArtifacts = getArtifactNames(
|
||||
target: target,
|
||||
libraryName: environment.crateInfo.packageName,
|
||||
remote: true,
|
||||
);
|
||||
final artifactsForTarget = <Artifact>[];
|
||||
|
||||
for (final artifact in requiredArtifacts) {
|
||||
final fileName = PrecompileBinaries.fileName(target, artifact);
|
||||
final downloadedPath = path.join(downloadedArtifactsDir, fileName);
|
||||
if (!File(downloadedPath).existsSync()) {
|
||||
final signatureFileName =
|
||||
PrecompileBinaries.signatureFileName(target, artifact);
|
||||
await _tryDownloadArtifacts(
|
||||
crateHash: crateHash,
|
||||
fileName: fileName,
|
||||
signatureFileName: signatureFileName,
|
||||
finalPath: downloadedPath,
|
||||
);
|
||||
}
|
||||
if (File(downloadedPath).existsSync()) {
|
||||
artifactsForTarget.add(Artifact(
|
||||
path: downloadedPath,
|
||||
finalFileName: artifact,
|
||||
));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Only provide complete set of artifacts.
|
||||
if (artifactsForTarget.length == requiredArtifacts.length) {
|
||||
_log.fine('Found precompiled artifacts for $target');
|
||||
res[target] = artifactsForTarget;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static Future<Response> _get(Uri url, {Map<String, String>? headers}) async {
|
||||
int attempt = 0;
|
||||
const maxAttempts = 10;
|
||||
while (true) {
|
||||
try {
|
||||
return await get(url, headers: headers);
|
||||
} on SocketException catch (e) {
|
||||
// Try to detect reset by peer error and retry.
|
||||
if (attempt++ < maxAttempts &&
|
||||
(e.osError?.errorCode == 54 || e.osError?.errorCode == 10054)) {
|
||||
_log.severe(
|
||||
'Failed to download $url: $e, attempt $attempt of $maxAttempts, will retry...');
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
continue;
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _tryDownloadArtifacts({
|
||||
required String crateHash,
|
||||
required String fileName,
|
||||
required String signatureFileName,
|
||||
required String finalPath,
|
||||
}) async {
|
||||
final precompiledBinaries = environment.crateOptions.precompiledBinaries!;
|
||||
final prefix = precompiledBinaries.uriPrefix;
|
||||
final url = Uri.parse('$prefix$crateHash/$fileName');
|
||||
final signatureUrl = Uri.parse('$prefix$crateHash/$signatureFileName');
|
||||
_log.fine('Downloading signature from $signatureUrl');
|
||||
final signature = await _get(signatureUrl);
|
||||
if (signature.statusCode == 404) {
|
||||
_log.warning(
|
||||
'Precompiled binaries not available for crate hash $crateHash ($fileName)');
|
||||
return;
|
||||
}
|
||||
if (signature.statusCode != 200) {
|
||||
_log.severe(
|
||||
'Failed to download signature $signatureUrl: status ${signature.statusCode}');
|
||||
return;
|
||||
}
|
||||
_log.fine('Downloading binary from $url');
|
||||
final res = await _get(url);
|
||||
if (res.statusCode != 200) {
|
||||
_log.severe('Failed to download binary $url: status ${res.statusCode}');
|
||||
return;
|
||||
}
|
||||
if (verify(
|
||||
precompiledBinaries.publicKey, res.bodyBytes, signature.bodyBytes)) {
|
||||
File(finalPath).writeAsBytesSync(res.bodyBytes);
|
||||
} else {
|
||||
_log.shout('Signature verification failed! Ignoring binary.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AritifactType {
|
||||
staticlib,
|
||||
dylib,
|
||||
}
|
||||
|
||||
AritifactType artifactTypeForTarget(Target target) {
|
||||
if (target.darwinPlatform != null) {
|
||||
return AritifactType.staticlib;
|
||||
} else {
|
||||
return AritifactType.dylib;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> getArtifactNames({
|
||||
required Target target,
|
||||
required String libraryName,
|
||||
required bool remote,
|
||||
AritifactType? aritifactType,
|
||||
}) {
|
||||
aritifactType ??= artifactTypeForTarget(target);
|
||||
if (target.darwinArch != null) {
|
||||
if (aritifactType == AritifactType.staticlib) {
|
||||
return ['lib$libraryName.a'];
|
||||
} else {
|
||||
return ['lib$libraryName.dylib'];
|
||||
}
|
||||
} else if (target.rust.contains('-windows-')) {
|
||||
if (aritifactType == AritifactType.staticlib) {
|
||||
return ['$libraryName.lib'];
|
||||
} else {
|
||||
return [
|
||||
'$libraryName.dll',
|
||||
'$libraryName.dll.lib',
|
||||
if (!remote) '$libraryName.pdb'
|
||||
];
|
||||
}
|
||||
} else if (target.rust.contains('-linux-')) {
|
||||
if (aritifactType == AritifactType.staticlib) {
|
||||
return ['lib$libraryName.a'];
|
||||
} else {
|
||||
return ['lib$libraryName.so'];
|
||||
}
|
||||
} else {
|
||||
throw Exception("Unsupported target: ${target.rust}");
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'artifacts_provider.dart';
|
||||
import 'builder.dart';
|
||||
import 'environment.dart';
|
||||
import 'options.dart';
|
||||
import 'target.dart';
|
||||
|
||||
class BuildCMake {
|
||||
final CargokitUserOptions userOptions;
|
||||
|
||||
BuildCMake({required this.userOptions});
|
||||
|
||||
Future<void> build() async {
|
||||
final targetPlatform = Environment.targetPlatform;
|
||||
final target = Target.forFlutterName(Environment.targetPlatform);
|
||||
if (target == null) {
|
||||
throw Exception("Unknown target platform: $targetPlatform");
|
||||
}
|
||||
|
||||
final environment = BuildEnvironment.fromEnvironment(isAndroid: false);
|
||||
final provider =
|
||||
ArtifactProvider(environment: environment, userOptions: userOptions);
|
||||
final artifacts = await provider.getArtifacts([target]);
|
||||
|
||||
final libs = artifacts[target]!;
|
||||
|
||||
for (final lib in libs) {
|
||||
if (lib.type == AritifactType.dylib) {
|
||||
File(lib.path)
|
||||
.copySync(path.join(Environment.outputDir, lib.finalFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'artifacts_provider.dart';
|
||||
import 'builder.dart';
|
||||
import 'environment.dart';
|
||||
import 'options.dart';
|
||||
import 'target.dart';
|
||||
|
||||
final log = Logger('build_gradle');
|
||||
|
||||
class BuildGradle {
|
||||
BuildGradle({required this.userOptions});
|
||||
|
||||
final CargokitUserOptions userOptions;
|
||||
|
||||
Future<void> build() async {
|
||||
final targets = Environment.targetPlatforms.map((arch) {
|
||||
final target = Target.forFlutterName(arch);
|
||||
if (target == null) {
|
||||
throw Exception(
|
||||
"Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}");
|
||||
}
|
||||
return target;
|
||||
}).toList();
|
||||
|
||||
final environment = BuildEnvironment.fromEnvironment(isAndroid: true);
|
||||
final provider =
|
||||
ArtifactProvider(environment: environment, userOptions: userOptions);
|
||||
final artifacts = await provider.getArtifacts(targets);
|
||||
|
||||
for (final target in targets) {
|
||||
final libs = artifacts[target]!;
|
||||
final outputDir = path.join(Environment.outputDir, target.android!);
|
||||
Directory(outputDir).createSync(recursive: true);
|
||||
|
||||
for (final lib in libs) {
|
||||
if (lib.type == AritifactType.dylib) {
|
||||
File(lib.path).copySync(path.join(outputDir, lib.finalFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'artifacts_provider.dart';
|
||||
import 'builder.dart';
|
||||
import 'environment.dart';
|
||||
import 'options.dart';
|
||||
import 'target.dart';
|
||||
import 'util.dart';
|
||||
|
||||
class BuildPod {
|
||||
BuildPod({required this.userOptions});
|
||||
|
||||
final CargokitUserOptions userOptions;
|
||||
|
||||
Future<void> build() async {
|
||||
final targets = Environment.darwinArchs.map((arch) {
|
||||
final target = Target.forDarwin(
|
||||
platformName: Environment.darwinPlatformName, darwinAarch: arch);
|
||||
if (target == null) {
|
||||
throw Exception(
|
||||
"Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}");
|
||||
}
|
||||
return target;
|
||||
}).toList();
|
||||
|
||||
final environment = BuildEnvironment.fromEnvironment(isAndroid: false);
|
||||
final provider =
|
||||
ArtifactProvider(environment: environment, userOptions: userOptions);
|
||||
final artifacts = await provider.getArtifacts(targets);
|
||||
|
||||
void performLipo(String targetFile, Iterable<String> sourceFiles) {
|
||||
runCommand("lipo", [
|
||||
'-create',
|
||||
...sourceFiles,
|
||||
'-output',
|
||||
targetFile,
|
||||
]);
|
||||
}
|
||||
|
||||
final outputDir = Environment.outputDir;
|
||||
|
||||
Directory(outputDir).createSync(recursive: true);
|
||||
|
||||
final staticLibs = artifacts.values
|
||||
.expand((element) => element)
|
||||
.where((element) => element.type == AritifactType.staticlib)
|
||||
.toList();
|
||||
final dynamicLibs = artifacts.values
|
||||
.expand((element) => element)
|
||||
.where((element) => element.type == AritifactType.dylib)
|
||||
.toList();
|
||||
|
||||
final libName = environment.crateInfo.packageName;
|
||||
|
||||
// If there is static lib, use it and link it with pod
|
||||
if (staticLibs.isNotEmpty) {
|
||||
final finalTargetFile = path.join(outputDir, "lib$libName.a");
|
||||
performLipo(finalTargetFile, staticLibs.map((e) => e.path));
|
||||
} else {
|
||||
// Otherwise try to replace bundle dylib with our dylib
|
||||
final bundlePaths = [
|
||||
'$libName.framework/Versions/A/$libName',
|
||||
'$libName.framework/$libName',
|
||||
];
|
||||
|
||||
for (final bundlePath in bundlePaths) {
|
||||
final targetFile = path.join(outputDir, bundlePath);
|
||||
if (File(targetFile).existsSync()) {
|
||||
performLipo(targetFile, dynamicLibs.map((e) => e.path));
|
||||
|
||||
// Replace absolute id with @rpath one so that it works properly
|
||||
// when moved to Frameworks.
|
||||
runCommand("install_name_tool", [
|
||||
'-id',
|
||||
'@rpath/$bundlePath',
|
||||
targetFile,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw Exception('Unable to find bundle for dynamic library');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:ed25519_edwards/ed25519_edwards.dart';
|
||||
import 'package:github/github.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'android_environment.dart';
|
||||
import 'build_cmake.dart';
|
||||
import 'build_gradle.dart';
|
||||
import 'build_pod.dart';
|
||||
import 'logging.dart';
|
||||
import 'options.dart';
|
||||
import 'precompile_binaries.dart';
|
||||
import 'target.dart';
|
||||
import 'util.dart';
|
||||
import 'verify_binaries.dart';
|
||||
|
||||
final log = Logger('build_tool');
|
||||
|
||||
abstract class BuildCommand extends Command {
|
||||
Future<void> runBuildCommand(CargokitUserOptions options);
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
final options = CargokitUserOptions.load();
|
||||
|
||||
if (options.verboseLogging ||
|
||||
Platform.environment['CARGOKIT_VERBOSE'] == '1') {
|
||||
enableVerboseLogging();
|
||||
}
|
||||
|
||||
await runBuildCommand(options);
|
||||
}
|
||||
}
|
||||
|
||||
class BuildPodCommand extends BuildCommand {
|
||||
@override
|
||||
final name = 'build-pod';
|
||||
|
||||
@override
|
||||
final description = 'Build cocoa pod library';
|
||||
|
||||
@override
|
||||
Future<void> runBuildCommand(CargokitUserOptions options) async {
|
||||
final build = BuildPod(userOptions: options);
|
||||
await build.build();
|
||||
}
|
||||
}
|
||||
|
||||
class BuildGradleCommand extends BuildCommand {
|
||||
@override
|
||||
final name = 'build-gradle';
|
||||
|
||||
@override
|
||||
final description = 'Build android library';
|
||||
|
||||
@override
|
||||
Future<void> runBuildCommand(CargokitUserOptions options) async {
|
||||
final build = BuildGradle(userOptions: options);
|
||||
await build.build();
|
||||
}
|
||||
}
|
||||
|
||||
class BuildCMakeCommand extends BuildCommand {
|
||||
@override
|
||||
final name = 'build-cmake';
|
||||
|
||||
@override
|
||||
final description = 'Build CMake library';
|
||||
|
||||
@override
|
||||
Future<void> runBuildCommand(CargokitUserOptions options) async {
|
||||
final build = BuildCMake(userOptions: options);
|
||||
await build.build();
|
||||
}
|
||||
}
|
||||
|
||||
class GenKeyCommand extends Command {
|
||||
@override
|
||||
final name = 'gen-key';
|
||||
|
||||
@override
|
||||
final description = 'Generate key pair for signing precompiled binaries';
|
||||
|
||||
@override
|
||||
void run() {
|
||||
final kp = generateKey();
|
||||
final private = HEX.encode(kp.privateKey.bytes);
|
||||
final public = HEX.encode(kp.publicKey.bytes);
|
||||
print("Private Key: $private");
|
||||
print("Public Key: $public");
|
||||
}
|
||||
}
|
||||
|
||||
class PrecompileBinariesCommand extends Command {
|
||||
PrecompileBinariesCommand() {
|
||||
argParser
|
||||
..addOption(
|
||||
'repository',
|
||||
mandatory: true,
|
||||
help: 'Github repository slug in format owner/name',
|
||||
)
|
||||
..addOption(
|
||||
'manifest-dir',
|
||||
mandatory: true,
|
||||
help: 'Directory containing Cargo.toml',
|
||||
)
|
||||
..addMultiOption('target',
|
||||
help: 'Rust target triple of artifact to build.\n'
|
||||
'Can be specified multiple times or omitted in which case\n'
|
||||
'all targets for current platform will be built.')
|
||||
..addOption(
|
||||
'android-sdk-location',
|
||||
help: 'Location of Android SDK (if available)',
|
||||
)
|
||||
..addOption(
|
||||
'android-ndk-version',
|
||||
help: 'Android NDK version (if available)',
|
||||
)
|
||||
..addOption(
|
||||
'android-min-sdk-version',
|
||||
help: 'Android minimum rquired version (if available)',
|
||||
)
|
||||
..addOption(
|
||||
'temp-dir',
|
||||
help: 'Directory to store temporary build artifacts',
|
||||
)
|
||||
..addOption(
|
||||
'glibc-version',
|
||||
help: 'GLIBC version to use for linux builds',
|
||||
)
|
||||
..addFlag(
|
||||
"verbose",
|
||||
abbr: "v",
|
||||
defaultsTo: false,
|
||||
help: "Enable verbose logging",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final name = 'precompile-binaries';
|
||||
|
||||
@override
|
||||
final description = 'Prebuild and upload binaries\n'
|
||||
'Private key must be passed through PRIVATE_KEY environment variable. '
|
||||
'Use gen_key through generate priave key.\n'
|
||||
'Github token must be passed as GITHUB_TOKEN environment variable.\n';
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
final verbose = argResults!['verbose'] as bool;
|
||||
if (verbose) {
|
||||
enableVerboseLogging();
|
||||
}
|
||||
|
||||
final privateKeyString = Platform.environment['PRIVATE_KEY'];
|
||||
if (privateKeyString == null) {
|
||||
throw ArgumentError('Missing PRIVATE_KEY environment variable');
|
||||
}
|
||||
final githubToken = Platform.environment['GITHUB_TOKEN'];
|
||||
if (githubToken == null) {
|
||||
throw ArgumentError('Missing GITHUB_TOKEN environment variable');
|
||||
}
|
||||
final privateKey = HEX.decode(privateKeyString);
|
||||
if (privateKey.length != 64) {
|
||||
throw ArgumentError('Private key must be 64 bytes long');
|
||||
}
|
||||
final manifestDir = argResults!['manifest-dir'] as String;
|
||||
if (!Directory(manifestDir).existsSync()) {
|
||||
throw ArgumentError('Manifest directory does not exist: $manifestDir');
|
||||
}
|
||||
String? androidMinSdkVersionString =
|
||||
argResults!['android-min-sdk-version'] as String?;
|
||||
int? androidMinSdkVersion;
|
||||
if (androidMinSdkVersionString != null) {
|
||||
androidMinSdkVersion = int.tryParse(androidMinSdkVersionString);
|
||||
if (androidMinSdkVersion == null) {
|
||||
throw ArgumentError(
|
||||
'Invalid android-min-sdk-version: $androidMinSdkVersionString');
|
||||
}
|
||||
}
|
||||
final targetStrigns = argResults!['target'] as List<String>;
|
||||
final targets = targetStrigns.map((target) {
|
||||
final res = Target.forRustTriple(target);
|
||||
if (res == null) {
|
||||
throw ArgumentError('Invalid target: $target');
|
||||
}
|
||||
return res;
|
||||
}).toList(growable: false);
|
||||
final precompileBinaries = PrecompileBinaries(
|
||||
privateKey: PrivateKey(privateKey),
|
||||
githubToken: githubToken,
|
||||
manifestDir: manifestDir,
|
||||
repositorySlug: RepositorySlug.full(argResults!['repository'] as String),
|
||||
targets: targets,
|
||||
androidSdkLocation: argResults!['android-sdk-location'] as String?,
|
||||
androidNdkVersion: argResults!['android-ndk-version'] as String?,
|
||||
androidMinSdkVersion: androidMinSdkVersion,
|
||||
tempDir: argResults!['temp-dir'] as String?,
|
||||
glibcVersion: argResults!['glibc-version'] as String?,
|
||||
);
|
||||
|
||||
await precompileBinaries.run();
|
||||
}
|
||||
}
|
||||
|
||||
class VerifyBinariesCommand extends Command {
|
||||
VerifyBinariesCommand() {
|
||||
argParser.addOption(
|
||||
'manifest-dir',
|
||||
mandatory: true,
|
||||
help: 'Directory containing Cargo.toml',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final name = "verify-binaries";
|
||||
|
||||
@override
|
||||
final description = 'Verifies published binaries\n'
|
||||
'Checks whether there is a binary published for each targets\n'
|
||||
'and checks the signature.';
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
final manifestDir = argResults!['manifest-dir'] as String;
|
||||
final verifyBinaries = VerifyBinaries(
|
||||
manifestDir: manifestDir,
|
||||
);
|
||||
await verifyBinaries.run();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> runMain(List<String> args) async {
|
||||
try {
|
||||
// Init logging before options are loaded
|
||||
initLogging();
|
||||
|
||||
if (Platform.environment['_CARGOKIT_NDK_LINK_TARGET'] != null) {
|
||||
return AndroidEnvironment.clangLinkerWrapper(args);
|
||||
}
|
||||
|
||||
final runner = CommandRunner('build_tool', 'Cargokit built_tool')
|
||||
..addCommand(BuildPodCommand())
|
||||
..addCommand(BuildGradleCommand())
|
||||
..addCommand(BuildCMakeCommand())
|
||||
..addCommand(GenKeyCommand())
|
||||
..addCommand(PrecompileBinariesCommand())
|
||||
..addCommand(VerifyBinariesCommand());
|
||||
|
||||
await runner.run(args);
|
||||
} on ArgumentError catch (e) {
|
||||
stderr.writeln(e.toString());
|
||||
exit(1);
|
||||
} catch (e, s) {
|
||||
log.severe(kDoubleSeparator);
|
||||
log.severe('Cargokit BuildTool failed with error:');
|
||||
log.severe(kSeparator);
|
||||
log.severe(e);
|
||||
// This tells user to install Rust, there's no need to pollute the log with
|
||||
// stack trace.
|
||||
if (e is! RustupNotFoundException) {
|
||||
log.severe(kSeparator);
|
||||
log.severe(s);
|
||||
log.severe(kSeparator);
|
||||
log.severe('BuildTool arguments: $args');
|
||||
}
|
||||
log.severe(kDoubleSeparator);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'android_environment.dart';
|
||||
import 'cargo.dart';
|
||||
import 'environment.dart';
|
||||
import 'options.dart';
|
||||
import 'rustup.dart';
|
||||
import 'target.dart';
|
||||
import 'util.dart';
|
||||
|
||||
final _log = Logger('builder');
|
||||
|
||||
enum BuildConfiguration {
|
||||
debug,
|
||||
release,
|
||||
profile,
|
||||
}
|
||||
|
||||
extension on BuildConfiguration {
|
||||
bool get isDebug => this == BuildConfiguration.debug;
|
||||
String get rustName => switch (this) {
|
||||
BuildConfiguration.debug => 'debug',
|
||||
BuildConfiguration.release => 'release',
|
||||
BuildConfiguration.profile => 'release',
|
||||
};
|
||||
}
|
||||
|
||||
class BuildException implements Exception {
|
||||
final String message;
|
||||
|
||||
BuildException(this.message);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BuildException: $message';
|
||||
}
|
||||
}
|
||||
|
||||
class BuildEnvironment {
|
||||
final BuildConfiguration configuration;
|
||||
final CargokitCrateOptions crateOptions;
|
||||
final String targetTempDir;
|
||||
final String manifestDir;
|
||||
final CrateInfo crateInfo;
|
||||
|
||||
final bool isAndroid;
|
||||
final String? androidSdkPath;
|
||||
final String? androidNdkVersion;
|
||||
final int? androidMinSdkVersion;
|
||||
final String? javaHome;
|
||||
|
||||
final String? glibcVersion;
|
||||
|
||||
BuildEnvironment({
|
||||
required this.configuration,
|
||||
required this.crateOptions,
|
||||
required this.targetTempDir,
|
||||
required this.manifestDir,
|
||||
required this.crateInfo,
|
||||
required this.isAndroid,
|
||||
this.androidSdkPath,
|
||||
this.androidNdkVersion,
|
||||
this.androidMinSdkVersion,
|
||||
this.javaHome,
|
||||
this.glibcVersion,
|
||||
});
|
||||
|
||||
static BuildConfiguration parseBuildConfiguration(String value) {
|
||||
// XCode configuration adds the flavor to configuration name.
|
||||
final firstSegment = value.split('-').first;
|
||||
final buildConfiguration = BuildConfiguration.values.firstWhereOrNull(
|
||||
(e) => e.name == firstSegment,
|
||||
);
|
||||
if (buildConfiguration == null) {
|
||||
_log.warning('Unknown build configuraiton $value, will assume release');
|
||||
return BuildConfiguration.release;
|
||||
}
|
||||
return buildConfiguration;
|
||||
}
|
||||
|
||||
static BuildEnvironment fromEnvironment({
|
||||
required bool isAndroid,
|
||||
}) {
|
||||
final buildConfiguration =
|
||||
parseBuildConfiguration(Environment.configuration);
|
||||
final manifestDir = Environment.manifestDir;
|
||||
final crateOptions = CargokitCrateOptions.load(
|
||||
manifestDir: manifestDir,
|
||||
);
|
||||
final crateInfo = CrateInfo.load(manifestDir);
|
||||
return BuildEnvironment(
|
||||
configuration: buildConfiguration,
|
||||
crateOptions: crateOptions,
|
||||
targetTempDir: Environment.targetTempDir,
|
||||
manifestDir: manifestDir,
|
||||
crateInfo: crateInfo,
|
||||
isAndroid: isAndroid,
|
||||
androidSdkPath: isAndroid ? Environment.sdkPath : null,
|
||||
androidNdkVersion: isAndroid ? Environment.ndkVersion : null,
|
||||
androidMinSdkVersion:
|
||||
isAndroid ? int.parse(Environment.minSdkVersion) : null,
|
||||
javaHome: isAndroid ? Environment.javaHome : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RustBuilder {
|
||||
final Target target;
|
||||
final BuildEnvironment environment;
|
||||
|
||||
RustBuilder({
|
||||
required this.target,
|
||||
required this.environment,
|
||||
});
|
||||
|
||||
void prepare(
|
||||
Rustup rustup,
|
||||
) {
|
||||
final toolchain = _toolchain;
|
||||
if (rustup.installedTargets(toolchain) == null) {
|
||||
rustup.installToolchain(toolchain);
|
||||
}
|
||||
if (toolchain == 'nightly') {
|
||||
rustup.installRustSrcForNightly();
|
||||
}
|
||||
if (!rustup.installedTargets(toolchain)!.contains(target.rust)) {
|
||||
rustup.installTarget(target.rust, toolchain: toolchain);
|
||||
}
|
||||
if (environment.glibcVersion != null) {
|
||||
rustup.installZigBuild(toolchain);
|
||||
}
|
||||
}
|
||||
|
||||
CargoBuildOptions? get _buildOptions =>
|
||||
environment.crateOptions.cargo[environment.configuration];
|
||||
|
||||
String get _toolchain => _buildOptions?.toolchain.name ?? 'stable';
|
||||
|
||||
/// Returns the path of directory containing build artifacts.
|
||||
Future<String> build() async {
|
||||
final extraArgs = _buildOptions?.flags ?? [];
|
||||
final manifestPath = path.join(environment.manifestDir, 'Cargo.toml');
|
||||
runCommand(
|
||||
'rustup',
|
||||
[
|
||||
'run',
|
||||
_toolchain,
|
||||
'cargo',
|
||||
(target.android == null && environment.glibcVersion != null)
|
||||
? 'zigbuild'
|
||||
: 'build',
|
||||
...extraArgs,
|
||||
'--manifest-path',
|
||||
manifestPath,
|
||||
'-p',
|
||||
environment.crateInfo.packageName,
|
||||
if (!environment.configuration.isDebug) '--release',
|
||||
'--target',
|
||||
target.rust +
|
||||
((target.android == null && environment.glibcVersion != null)
|
||||
? '.${environment.glibcVersion!}'
|
||||
: ""),
|
||||
'--target-dir',
|
||||
environment.targetTempDir,
|
||||
],
|
||||
environment: await _buildEnvironment(),
|
||||
);
|
||||
return path.join(
|
||||
environment.targetTempDir,
|
||||
target.rust,
|
||||
environment.configuration.rustName,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, String>> _buildEnvironment() async {
|
||||
if (target.android == null) {
|
||||
return {};
|
||||
} else {
|
||||
final sdkPath = environment.androidSdkPath;
|
||||
final ndkVersion = environment.androidNdkVersion;
|
||||
final minSdkVersion = environment.androidMinSdkVersion;
|
||||
if (sdkPath == null) {
|
||||
throw BuildException('androidSdkPath is not set');
|
||||
}
|
||||
if (ndkVersion == null) {
|
||||
throw BuildException('androidNdkVersion is not set');
|
||||
}
|
||||
if (minSdkVersion == null) {
|
||||
throw BuildException('androidMinSdkVersion is not set');
|
||||
}
|
||||
final env = AndroidEnvironment(
|
||||
sdkPath: sdkPath,
|
||||
ndkVersion: ndkVersion,
|
||||
minSdkVersion: minSdkVersion,
|
||||
targetTempDir: environment.targetTempDir,
|
||||
target: target,
|
||||
);
|
||||
if (!env.ndkIsInstalled() && environment.javaHome != null) {
|
||||
env.installNdk(javaHome: environment.javaHome!);
|
||||
}
|
||||
return env.buildEnvironment();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:toml/toml.dart';
|
||||
|
||||
class ManifestException {
|
||||
ManifestException(this.message, {required this.fileName});
|
||||
|
||||
final String? fileName;
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (fileName != null) {
|
||||
return 'Failed to parse package manifest at $fileName: $message';
|
||||
} else {
|
||||
return 'Failed to parse package manifest: $message';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CrateInfo {
|
||||
CrateInfo({required this.packageName});
|
||||
|
||||
final String packageName;
|
||||
|
||||
static CrateInfo parseManifest(String manifest, {final String? fileName}) {
|
||||
final toml = TomlDocument.parse(manifest);
|
||||
final package = toml.toMap()['package'];
|
||||
if (package == null) {
|
||||
throw ManifestException('Missing package section', fileName: fileName);
|
||||
}
|
||||
final name = package['name'];
|
||||
if (name == null) {
|
||||
throw ManifestException('Missing package name', fileName: fileName);
|
||||
}
|
||||
return CrateInfo(packageName: name);
|
||||
}
|
||||
|
||||
static CrateInfo load(String manifestDir) {
|
||||
final manifestFile = File(path.join(manifestDir, 'Cargo.toml'));
|
||||
final manifest = manifestFile.readAsStringSync();
|
||||
return parseManifest(manifest, fileName: manifestFile.path);
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class CrateHash {
|
||||
/// Computes a hash uniquely identifying crate content. This takes into account
|
||||
/// content all all .rs files inside the src directory, as well as Cargo.toml,
|
||||
/// Cargo.lock, build.rs and cargokit.yaml.
|
||||
///
|
||||
/// If [tempStorage] is provided, computed hash is stored in a file in that directory
|
||||
/// and reused on subsequent calls if the crate content hasn't changed.
|
||||
static String compute(String manifestDir, {String? tempStorage}) {
|
||||
return CrateHash._(
|
||||
manifestDir: manifestDir,
|
||||
tempStorage: tempStorage,
|
||||
)._compute();
|
||||
}
|
||||
|
||||
CrateHash._({
|
||||
required this.manifestDir,
|
||||
required this.tempStorage,
|
||||
});
|
||||
|
||||
String _compute() {
|
||||
final files = getFiles();
|
||||
final tempStorage = this.tempStorage;
|
||||
if (tempStorage != null) {
|
||||
final quickHash = _computeQuickHash(files);
|
||||
final quickHashFolder = Directory(path.join(tempStorage, 'crate_hash'));
|
||||
quickHashFolder.createSync(recursive: true);
|
||||
final quickHashFile = File(path.join(quickHashFolder.path, quickHash));
|
||||
if (quickHashFile.existsSync()) {
|
||||
return quickHashFile.readAsStringSync();
|
||||
}
|
||||
final hash = _computeHash(files);
|
||||
quickHashFile.writeAsStringSync(hash);
|
||||
return hash;
|
||||
} else {
|
||||
return _computeHash(files);
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes a quick hash based on files stat (without reading contents). This
|
||||
/// is used to cache the real hash, which is slower to compute since it involves
|
||||
/// reading every single file.
|
||||
String _computeQuickHash(List<File> files) {
|
||||
final output = AccumulatorSink<Digest>();
|
||||
final input = sha256.startChunkedConversion(output);
|
||||
|
||||
final data = ByteData(8);
|
||||
for (final file in files) {
|
||||
input.add(utf8.encode(file.path));
|
||||
final stat = file.statSync();
|
||||
data.setUint64(0, stat.size);
|
||||
input.add(data.buffer.asUint8List());
|
||||
data.setUint64(0, stat.modified.millisecondsSinceEpoch);
|
||||
input.add(data.buffer.asUint8List());
|
||||
}
|
||||
|
||||
input.close();
|
||||
return base64Url.encode(output.events.single.bytes);
|
||||
}
|
||||
|
||||
String _computeHash(List<File> files) {
|
||||
final output = AccumulatorSink<Digest>();
|
||||
final input = sha256.startChunkedConversion(output);
|
||||
|
||||
void addTextFile(File file) {
|
||||
// text Files are hashed by lines in case we're dealing with github checkout
|
||||
// that auto-converts line endings.
|
||||
final splitter = LineSplitter();
|
||||
if (file.existsSync()) {
|
||||
final data = file.readAsStringSync();
|
||||
final lines = splitter.convert(data);
|
||||
for (final line in lines) {
|
||||
input.add(utf8.encode(line));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final file in files) {
|
||||
addTextFile(file);
|
||||
}
|
||||
|
||||
input.close();
|
||||
final res = output.events.single;
|
||||
|
||||
// Truncate to 128bits.
|
||||
final hash = res.bytes.sublist(0, 16);
|
||||
return hex.encode(hash);
|
||||
}
|
||||
|
||||
List<File> getFiles() {
|
||||
final src = Directory(path.join(manifestDir, 'src'));
|
||||
final files = src
|
||||
.listSync(recursive: true, followLinks: false)
|
||||
.whereType<File>()
|
||||
.toList();
|
||||
files.sortBy((element) => element.path);
|
||||
void addFile(String relative) {
|
||||
final file = File(path.join(manifestDir, relative));
|
||||
if (file.existsSync()) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
addFile('Cargo.toml');
|
||||
addFile('Cargo.lock');
|
||||
addFile('build.rs');
|
||||
addFile('cargokit.yaml');
|
||||
return files;
|
||||
}
|
||||
|
||||
final String manifestDir;
|
||||
final String? tempStorage;
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
extension on String {
|
||||
String resolveSymlink() => File(this).resolveSymbolicLinksSync();
|
||||
}
|
||||
|
||||
class Environment {
|
||||
/// Current build configuration (debug or release).
|
||||
static String get configuration =>
|
||||
_getEnv("CARGOKIT_CONFIGURATION").toLowerCase();
|
||||
|
||||
static bool get isDebug => configuration == 'debug';
|
||||
static bool get isRelease => configuration == 'release';
|
||||
|
||||
/// Temporary directory where Rust build artifacts are placed.
|
||||
static String get targetTempDir => _getEnv("CARGOKIT_TARGET_TEMP_DIR");
|
||||
|
||||
/// Final output directory where the build artifacts are placed.
|
||||
static String get outputDir => _getEnvPath('CARGOKIT_OUTPUT_DIR');
|
||||
|
||||
/// Path to the crate manifest (containing Cargo.toml).
|
||||
static String get manifestDir => _getEnvPath('CARGOKIT_MANIFEST_DIR');
|
||||
|
||||
/// Directory inside root project. Not necessarily root folder. Symlinks are
|
||||
/// not resolved on purpose.
|
||||
static String get rootProjectDir => _getEnv('CARGOKIT_ROOT_PROJECT_DIR');
|
||||
|
||||
// Pod
|
||||
|
||||
/// Platform name (macosx, iphoneos, iphonesimulator).
|
||||
static String get darwinPlatformName =>
|
||||
_getEnv("CARGOKIT_DARWIN_PLATFORM_NAME");
|
||||
|
||||
/// List of architectures to build for (arm64, armv7, x86_64).
|
||||
static List<String> get darwinArchs =>
|
||||
_getEnv("CARGOKIT_DARWIN_ARCHS").split(' ');
|
||||
|
||||
// Gradle
|
||||
static String get minSdkVersion => _getEnv("CARGOKIT_MIN_SDK_VERSION");
|
||||
static String get ndkVersion => _getEnv("CARGOKIT_NDK_VERSION");
|
||||
static String get sdkPath => _getEnvPath("CARGOKIT_SDK_DIR");
|
||||
static String get javaHome => _getEnvPath("CARGOKIT_JAVA_HOME");
|
||||
static List<String> get targetPlatforms =>
|
||||
_getEnv("CARGOKIT_TARGET_PLATFORMS").split(',');
|
||||
|
||||
// CMAKE
|
||||
static String get targetPlatform => _getEnv("CARGOKIT_TARGET_PLATFORM");
|
||||
|
||||
static String _getEnv(String key) {
|
||||
final res = Platform.environment[key];
|
||||
if (res == null) {
|
||||
throw Exception("Missing environment variable $key");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static String _getEnvPath(String key) {
|
||||
final res = _getEnv(key);
|
||||
if (Directory(res).existsSync()) {
|
||||
return res.resolveSymlink();
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
const String kSeparator = "--";
|
||||
const String kDoubleSeparator = "==";
|
||||
|
||||
bool _lastMessageWasSeparator = false;
|
||||
|
||||
void _log(LogRecord rec) {
|
||||
final prefix = '${rec.level.name}: ';
|
||||
final out = rec.level == Level.SEVERE ? stderr : stdout;
|
||||
if (rec.message == kSeparator) {
|
||||
if (!_lastMessageWasSeparator) {
|
||||
out.write(prefix);
|
||||
out.writeln('-' * 80);
|
||||
_lastMessageWasSeparator = true;
|
||||
}
|
||||
return;
|
||||
} else if (rec.message == kDoubleSeparator) {
|
||||
out.write(prefix);
|
||||
out.writeln('=' * 80);
|
||||
_lastMessageWasSeparator = true;
|
||||
return;
|
||||
}
|
||||
out.write(prefix);
|
||||
out.writeln(rec.message);
|
||||
_lastMessageWasSeparator = false;
|
||||
}
|
||||
|
||||
void initLogging() {
|
||||
Logger.root.level = Level.INFO;
|
||||
Logger.root.onRecord.listen((LogRecord rec) {
|
||||
final lines = rec.message.split('\n');
|
||||
for (final line in lines) {
|
||||
if (line.isNotEmpty || lines.length == 1 || line != lines.last) {
|
||||
_log(LogRecord(
|
||||
rec.level,
|
||||
line,
|
||||
rec.loggerName,
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void enableVerboseLogging() {
|
||||
Logger.root.level = Level.ALL;
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ed25519_edwards/ed25519_edwards.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import 'builder.dart';
|
||||
import 'environment.dart';
|
||||
import 'rustup.dart';
|
||||
|
||||
final _log = Logger('options');
|
||||
|
||||
/// A class for exceptions that have source span information attached.
|
||||
class SourceSpanException implements Exception {
|
||||
// This is a getter so that subclasses can override it.
|
||||
/// A message describing the exception.
|
||||
String get message => _message;
|
||||
final String _message;
|
||||
|
||||
// This is a getter so that subclasses can override it.
|
||||
/// The span associated with this exception.
|
||||
///
|
||||
/// This may be `null` if the source location can't be determined.
|
||||
SourceSpan? get span => _span;
|
||||
final SourceSpan? _span;
|
||||
|
||||
SourceSpanException(this._message, this._span);
|
||||
|
||||
/// Returns a string representation of `this`.
|
||||
///
|
||||
/// [color] may either be a [String], a [bool], or `null`. If it's a string,
|
||||
/// it indicates an ANSI terminal color escape that should be used to
|
||||
/// highlight the span's text. If it's `true`, it indicates that the text
|
||||
/// should be highlighted using the default color. If it's `false` or `null`,
|
||||
/// it indicates that the text shouldn't be highlighted.
|
||||
@override
|
||||
String toString({Object? color}) {
|
||||
if (span == null) return message;
|
||||
return 'Error on ${span!.message(message, color: color)}';
|
||||
}
|
||||
}
|
||||
|
||||
enum Toolchain {
|
||||
stable,
|
||||
beta,
|
||||
nightly,
|
||||
}
|
||||
|
||||
class CargoBuildOptions {
|
||||
final Toolchain toolchain;
|
||||
final List<String> flags;
|
||||
|
||||
CargoBuildOptions({
|
||||
required this.toolchain,
|
||||
required this.flags,
|
||||
});
|
||||
|
||||
static Toolchain _toolchainFromNode(YamlNode node) {
|
||||
if (node case YamlScalar(value: String name)) {
|
||||
final toolchain =
|
||||
Toolchain.values.firstWhereOrNull((element) => element.name == name);
|
||||
if (toolchain != null) {
|
||||
return toolchain;
|
||||
}
|
||||
}
|
||||
throw SourceSpanException(
|
||||
'Unknown toolchain. Must be one of ${Toolchain.values.map((e) => e.name)}.',
|
||||
node.span);
|
||||
}
|
||||
|
||||
static CargoBuildOptions parse(YamlNode node) {
|
||||
if (node is! YamlMap) {
|
||||
throw SourceSpanException('Cargo options must be a map', node.span);
|
||||
}
|
||||
Toolchain toolchain = Toolchain.stable;
|
||||
List<String> flags = [];
|
||||
for (final MapEntry(:key, :value) in node.nodes.entries) {
|
||||
if (key case YamlScalar(value: 'toolchain')) {
|
||||
toolchain = _toolchainFromNode(value);
|
||||
} else if (key case YamlScalar(value: 'extra_flags')) {
|
||||
if (value case YamlList(nodes: List<YamlNode> list)) {
|
||||
if (list.every((element) {
|
||||
if (element case YamlScalar(value: String _)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})) {
|
||||
flags = list.map((e) => e.value as String).toList();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw SourceSpanException(
|
||||
'Extra flags must be a list of strings', value.span);
|
||||
} else {
|
||||
throw SourceSpanException(
|
||||
'Unknown cargo option type. Must be "toolchain" or "extra_flags".',
|
||||
key.span);
|
||||
}
|
||||
}
|
||||
return CargoBuildOptions(toolchain: toolchain, flags: flags);
|
||||
}
|
||||
}
|
||||
|
||||
extension on YamlMap {
|
||||
/// Map that extracts keys so that we can do map case check on them.
|
||||
Map<dynamic, YamlNode> get valueMap =>
|
||||
nodes.map((key, value) => MapEntry(key.value, value));
|
||||
}
|
||||
|
||||
class PrecompiledBinaries {
|
||||
final String uriPrefix;
|
||||
final PublicKey publicKey;
|
||||
|
||||
PrecompiledBinaries({
|
||||
required this.uriPrefix,
|
||||
required this.publicKey,
|
||||
});
|
||||
|
||||
static PublicKey _publicKeyFromHex(String key, SourceSpan? span) {
|
||||
final bytes = HEX.decode(key);
|
||||
if (bytes.length != 32) {
|
||||
throw SourceSpanException(
|
||||
'Invalid public key. Must be 32 bytes long.', span);
|
||||
}
|
||||
return PublicKey(bytes);
|
||||
}
|
||||
|
||||
static PrecompiledBinaries parse(YamlNode node) {
|
||||
if (node case YamlMap(valueMap: Map<dynamic, YamlNode> map)) {
|
||||
if (map
|
||||
case {
|
||||
'url_prefix': YamlNode urlPrefixNode,
|
||||
'public_key': YamlNode publicKeyNode,
|
||||
}) {
|
||||
final urlPrefix = switch (urlPrefixNode) {
|
||||
YamlScalar(value: String urlPrefix) => urlPrefix,
|
||||
_ => throw SourceSpanException(
|
||||
'Invalid URL prefix value.', urlPrefixNode.span),
|
||||
};
|
||||
final publicKey = switch (publicKeyNode) {
|
||||
YamlScalar(value: String publicKey) =>
|
||||
_publicKeyFromHex(publicKey, publicKeyNode.span),
|
||||
_ => throw SourceSpanException(
|
||||
'Invalid public key value.', publicKeyNode.span),
|
||||
};
|
||||
return PrecompiledBinaries(
|
||||
uriPrefix: urlPrefix,
|
||||
publicKey: publicKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
throw SourceSpanException(
|
||||
'Invalid precompiled binaries value. '
|
||||
'Expected Map with "url_prefix" and "public_key".',
|
||||
node.span);
|
||||
}
|
||||
}
|
||||
|
||||
/// Cargokit options specified for Rust crate.
|
||||
class CargokitCrateOptions {
|
||||
CargokitCrateOptions({
|
||||
this.cargo = const {},
|
||||
this.precompiledBinaries,
|
||||
});
|
||||
|
||||
final Map<BuildConfiguration, CargoBuildOptions> cargo;
|
||||
final PrecompiledBinaries? precompiledBinaries;
|
||||
|
||||
static CargokitCrateOptions parse(YamlNode node) {
|
||||
if (node is! YamlMap) {
|
||||
throw SourceSpanException('Cargokit options must be a map', node.span);
|
||||
}
|
||||
final options = <BuildConfiguration, CargoBuildOptions>{};
|
||||
PrecompiledBinaries? precompiledBinaries;
|
||||
|
||||
for (final entry in node.nodes.entries) {
|
||||
if (entry
|
||||
case MapEntry(
|
||||
key: YamlScalar(value: 'cargo'),
|
||||
value: YamlNode node,
|
||||
)) {
|
||||
if (node is! YamlMap) {
|
||||
throw SourceSpanException('Cargo options must be a map', node.span);
|
||||
}
|
||||
for (final MapEntry(:YamlNode key, :value) in node.nodes.entries) {
|
||||
if (key case YamlScalar(value: String name)) {
|
||||
final configuration = BuildConfiguration.values
|
||||
.firstWhereOrNull((element) => element.name == name);
|
||||
if (configuration != null) {
|
||||
options[configuration] = CargoBuildOptions.parse(value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw SourceSpanException(
|
||||
'Unknown build configuration. Must be one of ${BuildConfiguration.values.map((e) => e.name)}.',
|
||||
key.span);
|
||||
}
|
||||
} else if (entry.key case YamlScalar(value: 'precompiled_binaries')) {
|
||||
precompiledBinaries = PrecompiledBinaries.parse(entry.value);
|
||||
} else {
|
||||
throw SourceSpanException(
|
||||
'Unknown cargokit option type. Must be "cargo" or "precompiled_binaries".',
|
||||
entry.key.span);
|
||||
}
|
||||
}
|
||||
return CargokitCrateOptions(
|
||||
cargo: options,
|
||||
precompiledBinaries: precompiledBinaries,
|
||||
);
|
||||
}
|
||||
|
||||
static CargokitCrateOptions load({
|
||||
required String manifestDir,
|
||||
}) {
|
||||
final uri = Uri.file(path.join(manifestDir, "cargokit.yaml"));
|
||||
final file = File.fromUri(uri);
|
||||
if (file.existsSync()) {
|
||||
final contents = loadYamlNode(file.readAsStringSync(), sourceUrl: uri);
|
||||
return parse(contents);
|
||||
} else {
|
||||
return CargokitCrateOptions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CargokitUserOptions {
|
||||
// When Rustup is installed always build locally unless user opts into
|
||||
// using precompiled binaries.
|
||||
static bool defaultUsePrecompiledBinaries() {
|
||||
return Rustup.executablePath() == null;
|
||||
}
|
||||
|
||||
CargokitUserOptions({
|
||||
required this.usePrecompiledBinaries,
|
||||
required this.verboseLogging,
|
||||
});
|
||||
|
||||
CargokitUserOptions._()
|
||||
: usePrecompiledBinaries = defaultUsePrecompiledBinaries(),
|
||||
verboseLogging = false;
|
||||
|
||||
static CargokitUserOptions parse(YamlNode node) {
|
||||
if (node is! YamlMap) {
|
||||
throw SourceSpanException('Cargokit options must be a map', node.span);
|
||||
}
|
||||
bool usePrecompiledBinaries = defaultUsePrecompiledBinaries();
|
||||
bool verboseLogging = false;
|
||||
|
||||
for (final entry in node.nodes.entries) {
|
||||
if (entry.key case YamlScalar(value: 'use_precompiled_binaries')) {
|
||||
if (entry.value case YamlScalar(value: bool value)) {
|
||||
usePrecompiledBinaries = value;
|
||||
continue;
|
||||
}
|
||||
throw SourceSpanException(
|
||||
'Invalid value for "use_precompiled_binaries". Must be a boolean.',
|
||||
entry.value.span);
|
||||
} else if (entry.key case YamlScalar(value: 'verbose_logging')) {
|
||||
if (entry.value case YamlScalar(value: bool value)) {
|
||||
verboseLogging = value;
|
||||
continue;
|
||||
}
|
||||
throw SourceSpanException(
|
||||
'Invalid value for "verbose_logging". Must be a boolean.',
|
||||
entry.value.span);
|
||||
} else {
|
||||
throw SourceSpanException(
|
||||
'Unknown cargokit option type. Must be "use_precompiled_binaries" or "verbose_logging".',
|
||||
entry.key.span);
|
||||
}
|
||||
}
|
||||
return CargokitUserOptions(
|
||||
usePrecompiledBinaries: usePrecompiledBinaries,
|
||||
verboseLogging: verboseLogging,
|
||||
);
|
||||
}
|
||||
|
||||
static CargokitUserOptions load() {
|
||||
String fileName = "cargokit_options.yaml";
|
||||
var userProjectDir = Directory(Environment.rootProjectDir);
|
||||
|
||||
while (userProjectDir.parent.path != userProjectDir.path) {
|
||||
final configFile = File(path.join(userProjectDir.path, fileName));
|
||||
if (configFile.existsSync()) {
|
||||
final contents = loadYamlNode(
|
||||
configFile.readAsStringSync(),
|
||||
sourceUrl: configFile.uri,
|
||||
);
|
||||
final res = parse(contents);
|
||||
if (res.verboseLogging) {
|
||||
_log.info('Found user options file at ${configFile.path}');
|
||||
}
|
||||
return res;
|
||||
}
|
||||
userProjectDir = userProjectDir.parent;
|
||||
}
|
||||
return CargokitUserOptions._();
|
||||
}
|
||||
|
||||
final bool usePrecompiledBinaries;
|
||||
final bool verboseLogging;
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ed25519_edwards/ed25519_edwards.dart';
|
||||
import 'package:github/github.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'artifacts_provider.dart';
|
||||
import 'builder.dart';
|
||||
import 'cargo.dart';
|
||||
import 'crate_hash.dart';
|
||||
import 'options.dart';
|
||||
import 'rustup.dart';
|
||||
import 'target.dart';
|
||||
|
||||
final _log = Logger('precompile_binaries');
|
||||
|
||||
class PrecompileBinaries {
|
||||
PrecompileBinaries({
|
||||
required this.privateKey,
|
||||
required this.githubToken,
|
||||
required this.repositorySlug,
|
||||
required this.manifestDir,
|
||||
required this.targets,
|
||||
this.androidSdkLocation,
|
||||
this.androidNdkVersion,
|
||||
this.androidMinSdkVersion,
|
||||
this.tempDir,
|
||||
this.glibcVersion,
|
||||
});
|
||||
|
||||
final PrivateKey privateKey;
|
||||
final String githubToken;
|
||||
final RepositorySlug repositorySlug;
|
||||
final String manifestDir;
|
||||
final List<Target> targets;
|
||||
final String? androidSdkLocation;
|
||||
final String? androidNdkVersion;
|
||||
final int? androidMinSdkVersion;
|
||||
final String? tempDir;
|
||||
final String? glibcVersion;
|
||||
|
||||
static String fileName(Target target, String name) {
|
||||
return '${target.rust}_$name';
|
||||
}
|
||||
|
||||
static String signatureFileName(Target target, String name) {
|
||||
return '${target.rust}_$name.sig';
|
||||
}
|
||||
|
||||
Future<void> run() async {
|
||||
final crateInfo = CrateInfo.load(manifestDir);
|
||||
|
||||
final targets = List.of(this.targets);
|
||||
if (targets.isEmpty) {
|
||||
targets.addAll([
|
||||
...Target.buildableTargets(),
|
||||
if (androidSdkLocation != null) ...Target.androidTargets(),
|
||||
]);
|
||||
}
|
||||
|
||||
_log.info('Precompiling binaries for $targets');
|
||||
|
||||
final hash = CrateHash.compute(manifestDir);
|
||||
_log.info('Computed crate hash: $hash');
|
||||
|
||||
final String tagName = 'precompiled_$hash';
|
||||
|
||||
final github = GitHub(auth: Authentication.withToken(githubToken));
|
||||
final repo = github.repositories;
|
||||
final release = await _getOrCreateRelease(
|
||||
repo: repo,
|
||||
tagName: tagName,
|
||||
packageName: crateInfo.packageName,
|
||||
hash: hash,
|
||||
);
|
||||
|
||||
final tempDir = this.tempDir != null
|
||||
? Directory(this.tempDir!)
|
||||
: Directory.systemTemp.createTempSync('precompiled_');
|
||||
|
||||
tempDir.createSync(recursive: true);
|
||||
|
||||
final crateOptions = CargokitCrateOptions.load(
|
||||
manifestDir: manifestDir,
|
||||
);
|
||||
|
||||
final buildEnvironment = BuildEnvironment(
|
||||
configuration: BuildConfiguration.release,
|
||||
crateOptions: crateOptions,
|
||||
targetTempDir: tempDir.path,
|
||||
manifestDir: manifestDir,
|
||||
crateInfo: crateInfo,
|
||||
isAndroid: androidSdkLocation != null,
|
||||
androidSdkPath: androidSdkLocation,
|
||||
androidNdkVersion: androidNdkVersion,
|
||||
androidMinSdkVersion: androidMinSdkVersion,
|
||||
glibcVersion: glibcVersion,
|
||||
);
|
||||
|
||||
final rustup = Rustup();
|
||||
|
||||
for (final target in targets) {
|
||||
final artifactNames = getArtifactNames(
|
||||
target: target,
|
||||
libraryName: crateInfo.packageName,
|
||||
remote: true,
|
||||
);
|
||||
|
||||
if (artifactNames.every((name) {
|
||||
final fileName = PrecompileBinaries.fileName(target, name);
|
||||
return (release.assets ?? []).any((e) => e.name == fileName);
|
||||
})) {
|
||||
_log.info("All artifacts for $target already exist - skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
_log.info('Building for $target');
|
||||
|
||||
final builder =
|
||||
RustBuilder(target: target, environment: buildEnvironment);
|
||||
builder.prepare(rustup);
|
||||
final res = await builder.build();
|
||||
|
||||
final assets = <CreateReleaseAsset>[];
|
||||
for (final name in artifactNames) {
|
||||
final file = File(path.join(res, name));
|
||||
if (!file.existsSync()) {
|
||||
throw Exception('Missing artifact: ${file.path}');
|
||||
}
|
||||
|
||||
final data = file.readAsBytesSync();
|
||||
final create = CreateReleaseAsset(
|
||||
name: PrecompileBinaries.fileName(target, name),
|
||||
contentType: "application/octet-stream",
|
||||
assetData: data,
|
||||
);
|
||||
final signature = sign(privateKey, data);
|
||||
final signatureCreate = CreateReleaseAsset(
|
||||
name: signatureFileName(target, name),
|
||||
contentType: "application/octet-stream",
|
||||
assetData: signature,
|
||||
);
|
||||
bool verified = verify(public(privateKey), data, signature);
|
||||
if (!verified) {
|
||||
throw Exception('Signature verification failed');
|
||||
}
|
||||
assets.add(create);
|
||||
assets.add(signatureCreate);
|
||||
}
|
||||
_log.info('Uploading assets: ${assets.map((e) => e.name)}');
|
||||
for (final asset in assets) {
|
||||
// This seems to be failing on CI so do it one by one
|
||||
int retryCount = 0;
|
||||
while (true) {
|
||||
try {
|
||||
await repo.uploadReleaseAssets(release, [asset]);
|
||||
break;
|
||||
} on Exception catch (e) {
|
||||
if (retryCount == 10) {
|
||||
rethrow;
|
||||
}
|
||||
++retryCount;
|
||||
_log.shout(
|
||||
'Upload failed (attempt $retryCount, will retry): ${e.toString()}');
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_log.info('Cleaning up');
|
||||
tempDir.deleteSync(recursive: true);
|
||||
}
|
||||
|
||||
Future<Release> _getOrCreateRelease({
|
||||
required RepositoriesService repo,
|
||||
required String tagName,
|
||||
required String packageName,
|
||||
required String hash,
|
||||
}) async {
|
||||
Release release;
|
||||
try {
|
||||
_log.info('Fetching release $tagName');
|
||||
release = await repo.getReleaseByTagName(repositorySlug, tagName);
|
||||
} on ReleaseNotFound {
|
||||
_log.info('Release not found - creating release $tagName');
|
||||
release = await repo.createRelease(
|
||||
repositorySlug,
|
||||
CreateRelease.from(
|
||||
tagName: tagName,
|
||||
name: 'Precompiled binaries ${hash.substring(0, 8)}',
|
||||
targetCommitish: null,
|
||||
isDraft: false,
|
||||
isPrerelease: false,
|
||||
body: 'Precompiled binaries for crate $packageName, '
|
||||
'crate hash $hash.',
|
||||
));
|
||||
}
|
||||
return release;
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
class _Toolchain {
|
||||
_Toolchain(
|
||||
this.name,
|
||||
this.targets,
|
||||
);
|
||||
|
||||
final String name;
|
||||
final List<String> targets;
|
||||
}
|
||||
|
||||
class Rustup {
|
||||
List<String>? installedTargets(String toolchain) {
|
||||
final targets = _installedTargets(toolchain);
|
||||
return targets != null ? List.unmodifiable(targets) : null;
|
||||
}
|
||||
|
||||
void installToolchain(String toolchain) {
|
||||
log.info("Installing Rust toolchain: $toolchain");
|
||||
runCommand("rustup", ['toolchain', 'install', toolchain]);
|
||||
_installedToolchains
|
||||
.add(_Toolchain(toolchain, _getInstalledTargets(toolchain)));
|
||||
}
|
||||
|
||||
void installTarget(
|
||||
String target, {
|
||||
required String toolchain,
|
||||
}) {
|
||||
log.info("Installing Rust target: $target");
|
||||
runCommand("rustup", ['target', 'add', '--toolchain', toolchain, target]);
|
||||
_installedTargets(toolchain)?.add(target);
|
||||
}
|
||||
|
||||
bool _didInstallZigBuild = false;
|
||||
|
||||
void installZigBuild(String toolchain) {
|
||||
if (_didInstallZigBuild) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Installing Zig build");
|
||||
runCommand("rustup", [
|
||||
'run',
|
||||
toolchain,
|
||||
'cargo',
|
||||
'install',
|
||||
'--locked',
|
||||
'cargo-zigbuild',
|
||||
]);
|
||||
_didInstallZigBuild = true;
|
||||
}
|
||||
|
||||
final List<_Toolchain> _installedToolchains;
|
||||
|
||||
Rustup() : _installedToolchains = _getInstalledToolchains();
|
||||
|
||||
List<String>? _installedTargets(String toolchain) => _installedToolchains
|
||||
.firstWhereOrNull(
|
||||
(e) => e.name == toolchain || e.name.startsWith('$toolchain-'))
|
||||
?.targets;
|
||||
|
||||
static List<_Toolchain> _getInstalledToolchains() {
|
||||
String extractToolchainName(String line) {
|
||||
// ignore (default) after toolchain name
|
||||
final parts = line.split(' ');
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
final res = runCommand("rustup", ['toolchain', 'list']);
|
||||
|
||||
// To list all non-custom toolchains, we need to filter out lines that
|
||||
// don't start with "stable", "beta", or "nightly".
|
||||
Pattern nonCustom = RegExp(r"^(stable|beta|nightly)");
|
||||
final lines = res.stdout
|
||||
.toString()
|
||||
.split('\n')
|
||||
.where((e) => e.isNotEmpty && e.startsWith(nonCustom))
|
||||
.map(extractToolchainName)
|
||||
.toList(growable: true);
|
||||
|
||||
return lines
|
||||
.map(
|
||||
(name) => _Toolchain(
|
||||
name,
|
||||
_getInstalledTargets(name),
|
||||
),
|
||||
)
|
||||
.toList(growable: true);
|
||||
}
|
||||
|
||||
static List<String> _getInstalledTargets(String toolchain) {
|
||||
final res = runCommand("rustup", [
|
||||
'target',
|
||||
'list',
|
||||
'--toolchain',
|
||||
toolchain,
|
||||
'--installed',
|
||||
]);
|
||||
final lines = res.stdout
|
||||
.toString()
|
||||
.split('\n')
|
||||
.where((e) => e.isNotEmpty)
|
||||
.toList(growable: true);
|
||||
return lines;
|
||||
}
|
||||
|
||||
bool _didInstallRustSrcForNightly = false;
|
||||
|
||||
void installRustSrcForNightly() {
|
||||
if (_didInstallRustSrcForNightly) {
|
||||
return;
|
||||
}
|
||||
// Useful for -Z build-std
|
||||
runCommand(
|
||||
"rustup",
|
||||
['component', 'add', 'rust-src', '--toolchain', 'nightly'],
|
||||
);
|
||||
_didInstallRustSrcForNightly = true;
|
||||
}
|
||||
|
||||
static String? executablePath() {
|
||||
final envPath = Platform.environment['PATH'];
|
||||
final envPathSeparator = Platform.isWindows ? ';' : ':';
|
||||
final home = Platform.isWindows
|
||||
? Platform.environment['USERPROFILE']
|
||||
: Platform.environment['HOME'];
|
||||
final paths = [
|
||||
if (home != null) path.join(home, '.cargo', 'bin'),
|
||||
if (envPath != null) ...envPath.split(envPathSeparator),
|
||||
];
|
||||
for (final p in paths) {
|
||||
final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup';
|
||||
final rustupPath = path.join(p, rustup);
|
||||
if (File(rustupPath).existsSync()) {
|
||||
return rustupPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
class Target {
|
||||
Target({
|
||||
required this.rust,
|
||||
this.flutter,
|
||||
this.android,
|
||||
this.androidMinSdkVersion,
|
||||
this.darwinPlatform,
|
||||
this.darwinArch,
|
||||
});
|
||||
|
||||
static final all = [
|
||||
Target(
|
||||
rust: 'armv7-linux-androideabi',
|
||||
flutter: 'android-arm',
|
||||
android: 'armeabi-v7a',
|
||||
androidMinSdkVersion: 16,
|
||||
),
|
||||
Target(
|
||||
rust: 'aarch64-linux-android',
|
||||
flutter: 'android-arm64',
|
||||
android: 'arm64-v8a',
|
||||
androidMinSdkVersion: 21,
|
||||
),
|
||||
Target(
|
||||
rust: 'i686-linux-android',
|
||||
flutter: 'android-x86',
|
||||
android: 'x86',
|
||||
androidMinSdkVersion: 16,
|
||||
),
|
||||
Target(
|
||||
rust: 'x86_64-linux-android',
|
||||
flutter: 'android-x64',
|
||||
android: 'x86_64',
|
||||
androidMinSdkVersion: 21,
|
||||
),
|
||||
Target(
|
||||
rust: 'x86_64-pc-windows-msvc',
|
||||
flutter: 'windows-x64',
|
||||
),
|
||||
Target(
|
||||
rust: 'aarch64-pc-windows-msvc',
|
||||
flutter: 'windows-arm64',
|
||||
),
|
||||
Target(
|
||||
rust: 'x86_64-unknown-linux-gnu',
|
||||
flutter: 'linux-x64',
|
||||
),
|
||||
Target(
|
||||
rust: 'aarch64-unknown-linux-gnu',
|
||||
flutter: 'linux-arm64',
|
||||
),
|
||||
Target(rust: 'riscv64gc-unknown-linux-gnu', flutter: 'linux-riscv64'),
|
||||
Target(
|
||||
rust: 'x86_64-apple-darwin',
|
||||
darwinPlatform: 'macosx',
|
||||
darwinArch: 'x86_64',
|
||||
),
|
||||
Target(
|
||||
rust: 'aarch64-apple-darwin',
|
||||
darwinPlatform: 'macosx',
|
||||
darwinArch: 'arm64',
|
||||
),
|
||||
Target(
|
||||
rust: 'aarch64-apple-ios',
|
||||
darwinPlatform: 'iphoneos',
|
||||
darwinArch: 'arm64',
|
||||
),
|
||||
Target(
|
||||
rust: 'aarch64-apple-ios-sim',
|
||||
darwinPlatform: 'iphonesimulator',
|
||||
darwinArch: 'arm64',
|
||||
),
|
||||
Target(
|
||||
rust: 'x86_64-apple-ios',
|
||||
darwinPlatform: 'iphonesimulator',
|
||||
darwinArch: 'x86_64',
|
||||
),
|
||||
];
|
||||
|
||||
static Target? forFlutterName(String flutterName) {
|
||||
return all.firstWhereOrNull((element) => element.flutter == flutterName);
|
||||
}
|
||||
|
||||
static Target? forDarwin({
|
||||
required String platformName,
|
||||
required String darwinAarch,
|
||||
}) {
|
||||
return all.firstWhereOrNull((element) => //
|
||||
element.darwinPlatform == platformName &&
|
||||
element.darwinArch == darwinAarch);
|
||||
}
|
||||
|
||||
static Target? forRustTriple(String triple) {
|
||||
return all.firstWhereOrNull((element) => element.rust == triple);
|
||||
}
|
||||
|
||||
static List<Target> androidTargets() {
|
||||
return all
|
||||
.where((element) => element.android != null)
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
/// Returns buildable targets on current host platform ignoring Android targets.
|
||||
static List<Target> buildableTargets() {
|
||||
if (Platform.isLinux) {
|
||||
// Right now we don't support cross-compiling on Linux. So we just return
|
||||
// the host target.
|
||||
final arch = (runCommand('arch', []).stdout as String).trim();
|
||||
if (arch == 'aarch64') {
|
||||
return [Target.forRustTriple('aarch64-unknown-linux-gnu')!];
|
||||
} else if (arch == 'riscv64') {
|
||||
return [Target.forRustTriple('riscv64gc-unknown-linux-gnu')!];
|
||||
} else {
|
||||
return [Target.forRustTriple('x86_64-unknown-linux-gnu')!];
|
||||
}
|
||||
}
|
||||
return all.where((target) {
|
||||
if (Platform.isWindows) {
|
||||
return target.rust.contains('-windows-');
|
||||
} else if (Platform.isMacOS) {
|
||||
return target.darwinPlatform != null;
|
||||
}
|
||||
return false;
|
||||
}).toList(growable: false);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return rust;
|
||||
}
|
||||
|
||||
final String? flutter;
|
||||
final String rust;
|
||||
final String? android;
|
||||
final int? androidMinSdkVersion;
|
||||
final String? darwinPlatform;
|
||||
final String? darwinArch;
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'logging.dart';
|
||||
import 'rustup.dart';
|
||||
|
||||
final log = Logger("process");
|
||||
|
||||
class CommandFailedException implements Exception {
|
||||
final String executable;
|
||||
final List<String> arguments;
|
||||
final ProcessResult result;
|
||||
|
||||
CommandFailedException({
|
||||
required this.executable,
|
||||
required this.arguments,
|
||||
required this.result,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final stdout = result.stdout.toString().trim();
|
||||
final stderr = result.stderr.toString().trim();
|
||||
return [
|
||||
"External Command: $executable ${arguments.map((e) => '"$e"').join(' ')}",
|
||||
"Returned Exit Code: ${result.exitCode}",
|
||||
kSeparator,
|
||||
"STDOUT:",
|
||||
if (stdout.isNotEmpty) stdout,
|
||||
kSeparator,
|
||||
"STDERR:",
|
||||
if (stderr.isNotEmpty) stderr,
|
||||
].join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
class TestRunCommandArgs {
|
||||
final String executable;
|
||||
final List<String> arguments;
|
||||
final String? workingDirectory;
|
||||
final Map<String, String>? environment;
|
||||
final bool includeParentEnvironment;
|
||||
final bool runInShell;
|
||||
final Encoding? stdoutEncoding;
|
||||
final Encoding? stderrEncoding;
|
||||
|
||||
TestRunCommandArgs({
|
||||
required this.executable,
|
||||
required this.arguments,
|
||||
this.workingDirectory,
|
||||
this.environment,
|
||||
this.includeParentEnvironment = true,
|
||||
this.runInShell = false,
|
||||
this.stdoutEncoding,
|
||||
this.stderrEncoding,
|
||||
});
|
||||
}
|
||||
|
||||
class TestRunCommandResult {
|
||||
TestRunCommandResult({
|
||||
this.pid = 1,
|
||||
this.exitCode = 0,
|
||||
this.stdout = '',
|
||||
this.stderr = '',
|
||||
});
|
||||
|
||||
final int pid;
|
||||
final int exitCode;
|
||||
final String stdout;
|
||||
final String stderr;
|
||||
}
|
||||
|
||||
TestRunCommandResult Function(TestRunCommandArgs args)? testRunCommandOverride;
|
||||
|
||||
ProcessResult runCommand(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
String? workingDirectory,
|
||||
Map<String, String>? environment,
|
||||
bool includeParentEnvironment = true,
|
||||
bool runInShell = false,
|
||||
Encoding? stdoutEncoding = systemEncoding,
|
||||
Encoding? stderrEncoding = systemEncoding,
|
||||
}) {
|
||||
if (testRunCommandOverride != null) {
|
||||
final result = testRunCommandOverride!(TestRunCommandArgs(
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
includeParentEnvironment: includeParentEnvironment,
|
||||
runInShell: runInShell,
|
||||
stdoutEncoding: stdoutEncoding,
|
||||
stderrEncoding: stderrEncoding,
|
||||
));
|
||||
return ProcessResult(
|
||||
result.pid,
|
||||
result.exitCode,
|
||||
result.stdout,
|
||||
result.stderr,
|
||||
);
|
||||
}
|
||||
log.finer('Running command $executable ${arguments.join(' ')}');
|
||||
final res = Process.runSync(
|
||||
_resolveExecutable(executable),
|
||||
arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
includeParentEnvironment: includeParentEnvironment,
|
||||
runInShell: runInShell,
|
||||
stderrEncoding: stderrEncoding,
|
||||
stdoutEncoding: stdoutEncoding,
|
||||
);
|
||||
if (res.exitCode != 0) {
|
||||
throw CommandFailedException(
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
result: res,
|
||||
);
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
class RustupNotFoundException implements Exception {
|
||||
@override
|
||||
String toString() {
|
||||
return [
|
||||
' ',
|
||||
'rustup not found in PATH.',
|
||||
' ',
|
||||
'Maybe you need to install Rust? It only takes a minute:',
|
||||
' ',
|
||||
if (Platform.isWindows) 'https://www.rust-lang.org/tools/install',
|
||||
if (hasHomebrewRustInPath()) ...[
|
||||
'\$ brew unlink rust # Unlink homebrew Rust from PATH',
|
||||
],
|
||||
if (!Platform.isWindows)
|
||||
"\$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh",
|
||||
' ',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
static bool hasHomebrewRustInPath() {
|
||||
if (!Platform.isMacOS) {
|
||||
return false;
|
||||
}
|
||||
final envPath = Platform.environment['PATH'] ?? '';
|
||||
final paths = envPath.split(':');
|
||||
return paths.any((p) {
|
||||
return p.contains('homebrew') && File(path.join(p, 'rustc')).existsSync();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String _resolveExecutable(String executable) {
|
||||
if (executable == 'rustup') {
|
||||
final resolved = Rustup.executablePath();
|
||||
if (resolved != null) {
|
||||
return resolved;
|
||||
}
|
||||
throw RustupNotFoundException();
|
||||
} else {
|
||||
return executable;
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ed25519_edwards/ed25519_edwards.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'artifacts_provider.dart';
|
||||
import 'cargo.dart';
|
||||
import 'crate_hash.dart';
|
||||
import 'options.dart';
|
||||
import 'precompile_binaries.dart';
|
||||
import 'target.dart';
|
||||
|
||||
class VerifyBinaries {
|
||||
VerifyBinaries({
|
||||
required this.manifestDir,
|
||||
});
|
||||
|
||||
final String manifestDir;
|
||||
|
||||
Future<void> run() async {
|
||||
final crateInfo = CrateInfo.load(manifestDir);
|
||||
|
||||
final config = CargokitCrateOptions.load(manifestDir: manifestDir);
|
||||
final precompiledBinaries = config.precompiledBinaries;
|
||||
if (precompiledBinaries == null) {
|
||||
stdout.writeln('Crate does not support precompiled binaries.');
|
||||
} else {
|
||||
final crateHash = CrateHash.compute(manifestDir);
|
||||
stdout.writeln('Crate hash: $crateHash');
|
||||
|
||||
for (final target in Target.all) {
|
||||
final message = 'Checking ${target.rust}...';
|
||||
stdout.write(message.padRight(40));
|
||||
stdout.flush();
|
||||
|
||||
final artifacts = getArtifactNames(
|
||||
target: target,
|
||||
libraryName: crateInfo.packageName,
|
||||
remote: true,
|
||||
);
|
||||
|
||||
final prefix = precompiledBinaries.uriPrefix;
|
||||
|
||||
bool ok = true;
|
||||
|
||||
for (final artifact in artifacts) {
|
||||
final fileName = PrecompileBinaries.fileName(target, artifact);
|
||||
final signatureFileName =
|
||||
PrecompileBinaries.signatureFileName(target, artifact);
|
||||
|
||||
final url = Uri.parse('$prefix$crateHash/$fileName');
|
||||
final signatureUrl =
|
||||
Uri.parse('$prefix$crateHash/$signatureFileName');
|
||||
|
||||
final signature = await get(signatureUrl);
|
||||
if (signature.statusCode != 200) {
|
||||
stdout.writeln('MISSING');
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
final asset = await get(url);
|
||||
if (asset.statusCode != 200) {
|
||||
stdout.writeln('MISSING');
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!verify(precompiledBinaries.publicKey, asset.bodyBytes,
|
||||
signature.bodyBytes)) {
|
||||
stdout.writeln('INVALID SIGNATURE');
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
stdout.writeln('OK');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,453 +0,0 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "64.0.0"
|
||||
adaptive_number:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: adaptive_number
|
||||
sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.0"
|
||||
args:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: args
|
||||
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
collection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
convert:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: convert
|
||||
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.3"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
ed25519_edwards:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: ed25519_edwards
|
||||
sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
github:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: github
|
||||
sha256: "9966bc13bf612342e916b0a343e95e5f046c88f602a14476440e9b75d2295411"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.17.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
hex:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hex
|
||||
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.1"
|
||||
lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: lints
|
||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
logging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logging
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.12"
|
||||
source_span:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.24.6"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.6"
|
||||
toml:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: toml
|
||||
sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: version
|
||||
sha256: "2307e23a45b43f96469eeab946208ed63293e8afca9c28cd8b5241ff31c55f55"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.9.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
yaml:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,34 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: build_tool
|
||||
description: Cargokit build_tool. Facilitates the build of Rust crate during Flutter application build.
|
||||
publish_to: none
|
||||
version: 1.0.0
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
# Add regular dependencies here.
|
||||
dependencies:
|
||||
# these are pinned on purpose because the bundle_tool_runner doesn't have
|
||||
# pubspec.lock. See run_build_tool.sh
|
||||
logging: 1.2.0
|
||||
path: 1.8.0
|
||||
version: 3.0.0
|
||||
collection: 1.18.0
|
||||
ed25519_edwards: 0.3.1
|
||||
hex: 0.2.0
|
||||
yaml: 3.1.2
|
||||
source_span: 1.10.0
|
||||
github: 9.17.0
|
||||
args: 2.4.2
|
||||
crypto: 3.0.3
|
||||
convert: 3.1.1
|
||||
http: 1.1.0
|
||||
toml: 0.14.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.1.0
|
||||
test: ^1.24.0
|
||||
@@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'package:build_tool/src/builder.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('parseBuildConfiguration', () {
|
||||
var b = BuildEnvironment.parseBuildConfiguration('debug');
|
||||
expect(b, BuildConfiguration.debug);
|
||||
|
||||
b = BuildEnvironment.parseBuildConfiguration('profile');
|
||||
expect(b, BuildConfiguration.profile);
|
||||
|
||||
b = BuildEnvironment.parseBuildConfiguration('release');
|
||||
expect(b, BuildConfiguration.release);
|
||||
|
||||
b = BuildEnvironment.parseBuildConfiguration('debug-dev');
|
||||
expect(b, BuildConfiguration.debug);
|
||||
|
||||
b = BuildEnvironment.parseBuildConfiguration('profile');
|
||||
expect(b, BuildConfiguration.profile);
|
||||
|
||||
b = BuildEnvironment.parseBuildConfiguration('profile-prod');
|
||||
expect(b, BuildConfiguration.profile);
|
||||
|
||||
// fallback to release
|
||||
b = BuildEnvironment.parseBuildConfiguration('unknown');
|
||||
expect(b, BuildConfiguration.release);
|
||||
});
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'package:build_tool/src/cargo.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
final _cargoToml = """
|
||||
[workspace]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = "abort"
|
||||
opt-level = "z"
|
||||
# strip = "symbols"
|
||||
|
||||
[package]
|
||||
name = "super_native_extensions"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
""";
|
||||
|
||||
void main() {
|
||||
test('parseCargoToml', () {
|
||||
final info = CrateInfo.parseManifest(_cargoToml);
|
||||
expect(info.packageName, 'super_native_extensions');
|
||||
});
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'package:build_tool/src/builder.dart';
|
||||
import 'package:build_tool/src/options.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
void main() {
|
||||
test('parseCargoBuildOptions', () {
|
||||
final yaml = """
|
||||
toolchain: nightly
|
||||
extra_flags:
|
||||
- -Z
|
||||
# Comment here
|
||||
- build-std=panic_abort,std
|
||||
""";
|
||||
final node = loadYamlNode(yaml);
|
||||
final options = CargoBuildOptions.parse(node);
|
||||
expect(options.toolchain, Toolchain.nightly);
|
||||
expect(options.flags, ['-Z', 'build-std=panic_abort,std']);
|
||||
});
|
||||
|
||||
test('parsePrecompiledBinaries', () {
|
||||
final yaml = """
|
||||
url_prefix: https://url-prefix
|
||||
public_key: a4c3433798eb2c36edf2b94dbb4dd899d57496ca373a8982d8a792410b7f6445
|
||||
""";
|
||||
final precompiledBinaries = PrecompiledBinaries.parse(loadYamlNode(yaml));
|
||||
final key = HEX.decode(
|
||||
'a4c3433798eb2c36edf2b94dbb4dd899d57496ca373a8982d8a792410b7f6445');
|
||||
expect(precompiledBinaries.uriPrefix, 'https://url-prefix');
|
||||
expect(precompiledBinaries.publicKey.bytes, key);
|
||||
});
|
||||
|
||||
test('parseCargokitOptions', () {
|
||||
const yaml = '''
|
||||
cargo:
|
||||
# For smalles binaries rebuilt the standard library with panic=abort
|
||||
debug:
|
||||
toolchain: nightly
|
||||
extra_flags:
|
||||
- -Z
|
||||
# Comment here
|
||||
- build-std=panic_abort,std
|
||||
release:
|
||||
toolchain: beta
|
||||
|
||||
precompiled_binaries:
|
||||
url_prefix: https://url-prefix
|
||||
public_key: a4c3433798eb2c36edf2b94dbb4dd899d57496ca373a8982d8a792410b7f6445
|
||||
''';
|
||||
final options = CargokitCrateOptions.parse(loadYamlNode(yaml));
|
||||
expect(options.precompiledBinaries?.uriPrefix, 'https://url-prefix');
|
||||
final key = HEX.decode(
|
||||
'a4c3433798eb2c36edf2b94dbb4dd899d57496ca373a8982d8a792410b7f6445');
|
||||
expect(options.precompiledBinaries?.publicKey.bytes, key);
|
||||
|
||||
final debugOptions = options.cargo[BuildConfiguration.debug]!;
|
||||
expect(debugOptions.toolchain, Toolchain.nightly);
|
||||
expect(debugOptions.flags, ['-Z', 'build-std=panic_abort,std']);
|
||||
|
||||
final releaseOptions = options.cargo[BuildConfiguration.release]!;
|
||||
expect(releaseOptions.toolchain, Toolchain.beta);
|
||||
expect(releaseOptions.flags, []);
|
||||
});
|
||||
|
||||
test('parseCargokitUserOptions', () {
|
||||
const yaml = '''
|
||||
use_precompiled_binaries: false
|
||||
verbose_logging: true
|
||||
''';
|
||||
final options = CargokitUserOptions.parse(loadYamlNode(yaml));
|
||||
expect(options.usePrecompiledBinaries, false);
|
||||
expect(options.verboseLogging, true);
|
||||
});
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import 'package:build_tool/src/rustup.dart';
|
||||
import 'package:build_tool/src/util.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
test('rustup with no toolchains', () {
|
||||
bool didListToolchains = false;
|
||||
bool didInstallStable = false;
|
||||
bool didListTargets = false;
|
||||
testRunCommandOverride = (args) {
|
||||
expect(args.executable, 'rustup');
|
||||
switch (args.arguments) {
|
||||
case ['toolchain', 'list']:
|
||||
didListToolchains = true;
|
||||
return TestRunCommandResult(stdout: 'no installed toolchains\n');
|
||||
case ['toolchain', 'install', 'stable']:
|
||||
didInstallStable = true;
|
||||
return TestRunCommandResult();
|
||||
case ['target', 'list', '--toolchain', 'stable', '--installed']:
|
||||
didListTargets = true;
|
||||
return TestRunCommandResult(
|
||||
stdout: 'x86_64-unknown-linux-gnu\nx86_64-apple-darwin\n');
|
||||
default:
|
||||
throw Exception('Unexpected call: ${args.arguments}');
|
||||
}
|
||||
};
|
||||
final rustup = Rustup();
|
||||
rustup.installToolchain('stable');
|
||||
expect(didInstallStable, true);
|
||||
expect(didListToolchains, true);
|
||||
expect(didListTargets, true);
|
||||
expect(rustup.installedTargets('stable'), [
|
||||
'x86_64-unknown-linux-gnu',
|
||||
'x86_64-apple-darwin',
|
||||
]);
|
||||
testRunCommandOverride = null;
|
||||
});
|
||||
|
||||
test('rustup with esp toolchain', () {
|
||||
final targetsQueried = <String>[];
|
||||
testRunCommandOverride = (args) {
|
||||
expect(args.executable, 'rustup');
|
||||
switch (args.arguments) {
|
||||
case ['toolchain', 'list']:
|
||||
return TestRunCommandResult(
|
||||
stdout: 'stable-aarch64-apple-darwin (default)\n'
|
||||
'nightly-aarch64-apple-darwin\n'
|
||||
'esp\n');
|
||||
case ['target', 'list', '--toolchain', String toolchain, '--installed']:
|
||||
targetsQueried.add(toolchain);
|
||||
return TestRunCommandResult(stdout: '$toolchain:target\n');
|
||||
default:
|
||||
throw Exception('Unexpected call: ${args.arguments}');
|
||||
}
|
||||
};
|
||||
final rustup = Rustup();
|
||||
expect(targetsQueried, [
|
||||
'stable-aarch64-apple-darwin',
|
||||
'nightly-aarch64-apple-darwin',
|
||||
]);
|
||||
expect(rustup.installedTargets('stable'),
|
||||
['stable-aarch64-apple-darwin:target']);
|
||||
expect(rustup.installedTargets('nightly'),
|
||||
['nightly-aarch64-apple-darwin:target']);
|
||||
});
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
SET(cargokit_cmake_root "${CMAKE_CURRENT_LIST_DIR}/..")
|
||||
|
||||
# Workaround for https://github.com/dart-lang/pub/issues/4010
|
||||
get_filename_component(cargokit_cmake_root "${cargokit_cmake_root}" REALPATH)
|
||||
|
||||
if(WIN32)
|
||||
# REALPATH does not properly resolve symlinks on windows :-/
|
||||
execute_process(COMMAND powershell -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_LIST_DIR}/resolve_symlinks.ps1" "${cargokit_cmake_root}" OUTPUT_VARIABLE cargokit_cmake_root OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
endif()
|
||||
|
||||
# Arguments
|
||||
# - target: CMAKE target to which rust library is linked
|
||||
# - manifest_dir: relative path from current folder to directory containing cargo manifest
|
||||
# - lib_name: cargo package name
|
||||
# - any_symbol_name: name of any exported symbol from the library.
|
||||
# used on windows to force linking with library.
|
||||
function(apply_cargokit target manifest_dir lib_name any_symbol_name)
|
||||
|
||||
set(CARGOKIT_LIB_NAME "${lib_name}")
|
||||
set(CARGOKIT_LIB_FULL_NAME "${CMAKE_SHARED_MODULE_PREFIX}${CARGOKIT_LIB_NAME}${CMAKE_SHARED_MODULE_SUFFIX}")
|
||||
if (CMAKE_CONFIGURATION_TYPES)
|
||||
set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>")
|
||||
set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/${CARGOKIT_LIB_FULL_NAME}")
|
||||
else()
|
||||
set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/${CARGOKIT_LIB_FULL_NAME}")
|
||||
endif()
|
||||
set(CARGOKIT_TEMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/cargokit_build")
|
||||
|
||||
if (FLUTTER_TARGET_PLATFORM)
|
||||
set(CARGOKIT_TARGET_PLATFORM "${FLUTTER_TARGET_PLATFORM}")
|
||||
else()
|
||||
set(CARGOKIT_TARGET_PLATFORM "windows-x64")
|
||||
endif()
|
||||
|
||||
set(CARGOKIT_ENV
|
||||
"CARGOKIT_CMAKE=${CMAKE_COMMAND}"
|
||||
"CARGOKIT_CONFIGURATION=$<CONFIG>"
|
||||
"CARGOKIT_MANIFEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${manifest_dir}"
|
||||
"CARGOKIT_TARGET_TEMP_DIR=${CARGOKIT_TEMP_DIR}"
|
||||
"CARGOKIT_OUTPUT_DIR=${CARGOKIT_OUTPUT_DIR}"
|
||||
"CARGOKIT_TARGET_PLATFORM=${CARGOKIT_TARGET_PLATFORM}"
|
||||
"CARGOKIT_TOOL_TEMP_DIR=${CARGOKIT_TEMP_DIR}/tool"
|
||||
"CARGOKIT_ROOT_PROJECT_DIR=${CMAKE_SOURCE_DIR}"
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
set(SCRIPT_EXTENSION ".cmd")
|
||||
set(IMPORT_LIB_EXTENSION ".lib")
|
||||
else()
|
||||
set(SCRIPT_EXTENSION ".sh")
|
||||
set(IMPORT_LIB_EXTENSION "")
|
||||
execute_process(COMMAND chmod +x "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}")
|
||||
endif()
|
||||
|
||||
# Using generators in custom command is only supported in CMake 3.20+
|
||||
if (CMAKE_CONFIGURATION_TYPES AND ${CMAKE_VERSION} VERSION_LESS "3.20.0")
|
||||
foreach(CONFIG IN LISTS CMAKE_CONFIGURATION_TYPES)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${CONFIG}/${CARGOKIT_LIB_FULL_NAME}"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/_phony_"
|
||||
COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV}
|
||||
"${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake
|
||||
VERBATIM
|
||||
)
|
||||
endforeach()
|
||||
else()
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${OUTPUT_LIB}
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/_phony_"
|
||||
COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV}
|
||||
"${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/_phony_" PROPERTIES SYMBOLIC TRUE)
|
||||
|
||||
if (TARGET ${target})
|
||||
# If we have actual cmake target provided create target and make existing
|
||||
# target depend on it
|
||||
add_custom_target("${target}_cargokit" DEPENDS ${OUTPUT_LIB})
|
||||
add_dependencies("${target}" "${target}_cargokit")
|
||||
target_link_libraries("${target}" PRIVATE "${OUTPUT_LIB}${IMPORT_LIB_EXTENSION}")
|
||||
if(WIN32)
|
||||
target_link_options(${target} PRIVATE "/INCLUDE:${any_symbol_name}")
|
||||
endif()
|
||||
else()
|
||||
# Otherwise (FFI) just use ALL to force building always
|
||||
add_custom_target("${target}_cargokit" ALL DEPENDS ${OUTPUT_LIB})
|
||||
endif()
|
||||
|
||||
# Allow adding the output library to plugin bundled libraries
|
||||
set("${target}_cargokit_lib" ${OUTPUT_LIB} PARENT_SCOPE)
|
||||
|
||||
endfunction()
|
||||
@@ -1,38 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
function Resolve-Symlinks {
|
||||
[CmdletBinding()]
|
||||
[OutputType([string])]
|
||||
param(
|
||||
[Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
|
||||
[string] $Path
|
||||
)
|
||||
|
||||
[string] $separator = '/'
|
||||
[string[]] $parts = $Path.Split($separator)
|
||||
|
||||
[string] $realPath = ''
|
||||
foreach ($part in $parts) {
|
||||
if ($realPath -and !$realPath.EndsWith($separator)) {
|
||||
$realPath += $separator
|
||||
}
|
||||
|
||||
$realPath += $part.Replace('\', '/')
|
||||
|
||||
# The slash is important when using Get-Item on Drive letters in pwsh.
|
||||
if (-not($realPath.Contains($separator)) -and $realPath.EndsWith(':')) {
|
||||
$realPath += '/'
|
||||
}
|
||||
|
||||
$item = Get-Item $realPath
|
||||
if ($item.LinkTarget) {
|
||||
$realPath = $item.LinkTarget.Replace('\', '/')
|
||||
}
|
||||
}
|
||||
$realPath
|
||||
}
|
||||
|
||||
$path = Resolve-Symlinks -Path $args[0]
|
||||
Write-Host $path
|
||||
@@ -1,110 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
# Cargokit Architecture
|
||||
|
||||
Note: This is mostly relevant for plugins authors that want to see a bit under the hood rather then just following a tutorial.
|
||||
|
||||
In ideal conditions the end-developer using the plugin should not even be aware of Cargokit existence.
|
||||
|
||||
## Integration
|
||||
|
||||
Cargokit is meant to be included in Flutter plugin (or application) that contains the Rust crate to be built during the Flutter build process.
|
||||
|
||||
Cargokit can be either incuded as git submodule or git subtree (required for plugins - as pub does not support submodules for git dependencies).
|
||||
|
||||
For a step by step tutorial on integrating Cargokit with a Flutter plugin see https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/.
|
||||
|
||||
## build_tool
|
||||
|
||||
Build tool is the core of cargokit. It is a Dart command line package that facilitates the build of Rust crate. It is invoked during the Flutter build process to build (or download) Rust artifacts, but it can be also used as a standalone tool.
|
||||
|
||||
It handles the following commands:
|
||||
|
||||
### build-cmake
|
||||
|
||||
This is invoked from `cargokit.cmake` and it is used to build the Rust crate into a dynamic library on Linux and Windows (which use CMake as build system).
|
||||
|
||||
The command takes no additional arguments, everything is controlled during environment variables set by `cargokit.cmake`.
|
||||
|
||||
### build-gradle
|
||||
|
||||
This is invoked from `plugin.gradle` and it is used to build the Rust crate into a dynamic library on Android. The command takes no additional arguments, everything is controlled during environment variables set by `plugin.gradle`.
|
||||
|
||||
The build_tool installs NDK if needed, configures the Rust environment for cross compilation and then invokes `cargo build` with appropriate arguments and environment variables.
|
||||
|
||||
The build-tool also acts a linker driver.
|
||||
|
||||
### build-pod
|
||||
|
||||
This is invoked from plugin's podspec `script_phase` through `build_pod.sh`. Bundle tool will build the Rust crate into a static library that gets linked into the plugin Framework. In this case must have `:execution_position` set to `:before_compile`.
|
||||
|
||||
Cargokit will build binaries for all active architectures from XCode build and lipo them togherer.
|
||||
|
||||
When using Cargokit to integrate Rust code with an application (not a plugin) you can also configure the `Cargo.toml` to just build a dynamic library. When Cargokit finds that the crate only built a dylib and no static lib, it will attempt to replace the Cocoapod framework binary with the dylib. In this case the script `:execution_position` must be set to `:after_compile`. This is *not* recommended for plugins and it's quite experimental.
|
||||
|
||||
### gen-key, precompile-binaries, verify-binaries
|
||||
|
||||
These are used as when providing precompiled binaries for Plugin. See [precompiled_binaries.md](precompiled_binaries.md) for more information.
|
||||
|
||||
## Launching the build_tool during build.
|
||||
|
||||
During Flutter build, the build tool can not be launched directly using `dart run`. Rather it is launched through `run_build_tool.sh` and `run_build_tool.cmd`. Because the `build_tool` is shipped as part of plugin, we generally don't want to write into the plugin directory during build, which would happen if the `build_tool` was simply invoked through `dart run` (For example the `.dart_tool/package_config.json` file would get written inside the `build_tool` directory).
|
||||
|
||||
Instead the `run_build_tool` script creates a minimal Dart command line package in the build directory and references the `build_tool` as package. That way the `.dart_tool/package_config.json` file is created in the temporary build folder and not in the plugin itself. The script also precompiles the Dart code to speed up subsequent invocations.
|
||||
|
||||
## Configuring Cargokit
|
||||
|
||||
### Configuration for the Rust crate
|
||||
|
||||
Cargokit can be configured through a `cargokit.yaml` file, which can be used to control the build of the Rust package and is placed into the Rust crate next to `Cargo.toml`.
|
||||
|
||||
Here is an example `cargokit.yaml` with comments:
|
||||
```yaml
|
||||
cargo:
|
||||
debug: # Configuration of cargo execution during debug builds
|
||||
toolchain: stable # default
|
||||
release: # Configuration of cargo execution for release builds
|
||||
toolchain: nightly # rustup will be invoked with nightly toolchain
|
||||
extra_flags: # extra arguments passed to cargo build
|
||||
- -Z
|
||||
- build-std=panic_abort,std
|
||||
|
||||
# If crate ships with precompiled binaries, they can be configured here.
|
||||
precompiled_binaries:
|
||||
# Uri prefix used when downloading precompiled binaries.
|
||||
url_prefix: https://github.com/superlistapp/super_native_extensions/releases/download/precompiled_
|
||||
|
||||
# Public key for verifying downloaded precompiled binaries.
|
||||
public_key: 3a257ef1c7d72d84225ac4658d24812ada50a7a7a8a2138c2a91353389fdc514
|
||||
```
|
||||
|
||||
### Configuration for the application consuming the plugin
|
||||
|
||||
A `cargokit_options.yaml` file can also be placed by developer using plugin to the root of the application package. In which case the file can be used to specify following options:
|
||||
|
||||
```yaml
|
||||
# Enables verbose logging of Cargokit during build
|
||||
verbose_logging: true
|
||||
|
||||
# Opts out of using precompiled binaries. If crate has configured
|
||||
# and deployed precompiled binaries, these will be by default used whenever Rustup
|
||||
# is not installed. With `use_precompiled_binaries` set to false, the build will
|
||||
# instead be aborted prompting user to install Rustup.
|
||||
use_precompiled_binaries: false
|
||||
```
|
||||
|
||||
## Detecting Rustup
|
||||
|
||||
When the plugin doesn't come with precompiled libraries (or user opt-out), `build_tool` will need to invoke Rustup during build to ensure that required Rust targets and toolchain are installed for current build and to build the Rust crate.
|
||||
|
||||
Cargokit will attempt to detect Rustup in the default Rustup installation location (`~/.cargo/rustup`) as well as in PATH. This is done so that if user install Rustup but doesn't properly configure PATH, Cargokit will still work.
|
||||
|
||||
If `build_tool` doesn't find Rustup, it will about the build with a message showing instructions to install Rustup specific to current platform.
|
||||
|
||||
On macOS it will also detect a homebrew Rust installation in PATH and will prompt user to call `brew unlink rust` first to remove homebrew Rust installation from PATH, because it may interfere with Rustup.
|
||||
|
||||
Homebrew Rust installation can not be used by Cargokit, because it can only build for host platform. Cargokit needs to be able to cross compile the Rust crate for iOS and Android and thus needs full Rustup installation.
|
||||
@@ -1,101 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
|
||||
# Precompiled Binaries
|
||||
|
||||
Because Cargokit builds the Rust crate during Flutter build, it is inherently
|
||||
dependend on the Rust toolchain being installed on the developer's machine.
|
||||
|
||||
To decrease the friction, it is possible for Cargokit to use precompiled binaries instead.
|
||||
|
||||
This is how the process of using precompiled binaries looks from the perspective of the build on developer machine:
|
||||
|
||||
1. Cargokit checks if there is `cargokit_options.yaml` file in the root folder of target application. If there is one, it will be checked for `use_precompiled_binaries` options to see if user opted out of using precompiled binaries. In which case Cargokit will insist on building from source. Cargokit will also build from source if the configuration file is absent, but user has Rustup installed.
|
||||
|
||||
2. Cargokit checks if there is `cargokit.yaml` file placed in the Rust crate. If there is one, it will be checked for `precompiled_binaries` section to see if crate supports precompiled binaries. The configuration section must contain a public key and URL prefix.
|
||||
|
||||
3. Cargokit computes a `crate-hash`. This is a SHA256 hash value computed from all Rust files inside crate, `Cargo.toml`, `Cargo.lock` and `cargokit.yaml`. This uniquely identifies the crate and it is used to find the correct precompiled binaries.
|
||||
|
||||
4. Cargokit will attempt to download the precompiled binaries for target platform and `crate_hash` combination and a signature file for each downloaded binary. If download succeeds, the binary content will be verified against the signature and public key included in `cargokit.yaml` (which is part of Rust crate and thus part of published Flutter package).
|
||||
|
||||
5. If the verification succeeds, the precompiled binaries will be used. Otherwise the binary will be discarded and Cargokit will insist on building from source.
|
||||
|
||||
## Providing precompiled binaries
|
||||
|
||||
Note that this assumes that precompiled binaries will be generated during github actions and deployed as github releases.
|
||||
|
||||
### Use `build_tool` to generate a key-pair:
|
||||
|
||||
```
|
||||
dart run build_tool gen-key
|
||||
```
|
||||
|
||||
This will print the private key and public key. Store the private key securely. It needs to be provided as a secret to github action.
|
||||
|
||||
The public key should be included in `cargokit.yaml` file in the Rust crate.
|
||||
|
||||
### Provide a `cargokit.yaml` file in the Rust crate
|
||||
|
||||
The file must be placed alongside Cargo.toml.
|
||||
|
||||
```yaml
|
||||
precompiled_binaries:
|
||||
# Uri prefix used when downloading precompiled binaries.
|
||||
url_prefix: https://github.com/<repository-owner>/<repository-name>/releases/download/precompiled_
|
||||
|
||||
# Public key for verifying downloaded precompiled binaries.
|
||||
public_key: <public key from previous step>
|
||||
```
|
||||
|
||||
### Configure a github action to build and upload precompiled binaries.
|
||||
|
||||
The github action should be run at every commit to main branch (and possibly other branches).
|
||||
|
||||
The action needs two secrets - private key for signing binaries and GitHub token for uploading binaries as releases. Here is example action that precompiles and uploads binaries for all supported targets.
|
||||
|
||||
```yaml
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
name: Precompile Binaries
|
||||
|
||||
jobs:
|
||||
Precompile:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macOS-latest
|
||||
- windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: dart-lang/setup-dart@v1
|
||||
- name: Install GTK
|
||||
if: (matrix.os == 'ubuntu-latest')
|
||||
run: sudo apt-get update && sudo apt-get install libgtk-3-dev
|
||||
- name: Precompile
|
||||
if: (matrix.os == 'macOS-latest') || (matrix.os == 'windows-latest')
|
||||
run: dart run build_tool precompile-binaries -v --manifest-dir=../../rust --repository=superlistapp/super_native_extensions
|
||||
working-directory: super_native_extensions/cargokit/build_tool
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
|
||||
PRIVATE_KEY: ${{ secrets.RELEASE_PRIVATE_KEY }}
|
||||
- name: Precompile (with Android)
|
||||
if: (matrix.os == 'ubuntu-latest')
|
||||
run: dart run build_tool precompile-binaries -v --manifest-dir=../../rust --repository=superlistapp/super_native_extensions --android-sdk-location=/usr/local/lib/android/sdk --android-ndk-version=24.0.8215888 --android-min-sdk-version=23
|
||||
working-directory: super_native_extensions/cargokit/build_tool
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
|
||||
PRIVATE_KEY: ${{ secrets.RELEASE_PRIVATE_KEY }}
|
||||
```
|
||||
|
||||
By default the `built_tool precompile-binaries` commands build and uploads the binaries for all targets buildable from current host. This can be overriden using the `--target <rust-triple>` argument.
|
||||
|
||||
Android binaries will be built when `--android-sdk-location` and `--android-ndk-version` arguments are provided.
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import java.nio.file.Paths
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
CargoKitPlugin.file = buildscript.sourceFile
|
||||
|
||||
apply plugin: CargoKitPlugin
|
||||
|
||||
class CargoKitExtension {
|
||||
String manifestDir; // Relative path to folder containing Cargo.toml
|
||||
String libname; // Library name within Cargo.toml. Must be a cdylib
|
||||
}
|
||||
|
||||
abstract class CargoKitBuildTask extends DefaultTask {
|
||||
|
||||
@Input
|
||||
String buildMode
|
||||
|
||||
@Input
|
||||
String buildDir
|
||||
|
||||
@Input
|
||||
String outputDir
|
||||
|
||||
@Input
|
||||
String ndkVersion
|
||||
|
||||
@Input
|
||||
String sdkDirectory
|
||||
|
||||
@Input
|
||||
int compileSdkVersion;
|
||||
|
||||
@Input
|
||||
int minSdkVersion;
|
||||
|
||||
@Input
|
||||
String pluginFile
|
||||
|
||||
@Input
|
||||
List<String> targetPlatforms
|
||||
|
||||
@TaskAction
|
||||
def build() {
|
||||
if (project.cargokit.manifestDir == null) {
|
||||
throw new GradleException("Property 'manifestDir' must be set on cargokit extension");
|
||||
}
|
||||
|
||||
if (project.cargokit.libname == null) {
|
||||
throw new GradleException("Property 'libname' must be set on cargokit extension");
|
||||
}
|
||||
|
||||
def executableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "run_build_tool.cmd" : "run_build_tool.sh"
|
||||
def path = Paths.get(new File(pluginFile).parent, "..", executableName);
|
||||
|
||||
def manifestDir = Paths.get(project.buildscript.sourceFile.parent, project.cargokit.manifestDir)
|
||||
|
||||
def rootProjectDir = project.rootProject.projectDir
|
||||
|
||||
if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
project.exec {
|
||||
commandLine 'chmod', '+x', path
|
||||
}
|
||||
}
|
||||
|
||||
project.exec {
|
||||
executable path
|
||||
args "build-gradle"
|
||||
environment "CARGOKIT_ROOT_PROJECT_DIR", rootProjectDir
|
||||
environment "CARGOKIT_TOOL_TEMP_DIR", "${buildDir}/build_tool"
|
||||
environment "CARGOKIT_MANIFEST_DIR", manifestDir
|
||||
environment "CARGOKIT_CONFIGURATION", buildMode
|
||||
environment "CARGOKIT_TARGET_TEMP_DIR", buildDir
|
||||
environment "CARGOKIT_OUTPUT_DIR", outputDir
|
||||
environment "CARGOKIT_NDK_VERSION", ndkVersion
|
||||
environment "CARGOKIT_SDK_DIR", sdkDirectory
|
||||
environment "CARGOKIT_COMPILE_SDK_VERSION", compileSdkVersion
|
||||
environment "CARGOKIT_MIN_SDK_VERSION", minSdkVersion
|
||||
environment "CARGOKIT_TARGET_PLATFORMS", targetPlatforms.join(",")
|
||||
environment "CARGOKIT_JAVA_HOME", System.properties['java.home']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CargoKitPlugin implements Plugin<Project> {
|
||||
|
||||
static String file;
|
||||
|
||||
private Plugin findFlutterPlugin(Project rootProject) {
|
||||
_findFlutterPlugin(rootProject.childProjects)
|
||||
}
|
||||
|
||||
private Plugin _findFlutterPlugin(Map projects) {
|
||||
for (project in projects) {
|
||||
for (plugin in project.value.getPlugins()) {
|
||||
if (plugin.class.name == "com.flutter.gradle.FlutterPlugin") {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
def plugin = _findFlutterPlugin(project.value.childProjects);
|
||||
if (plugin != null) {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(Project project) {
|
||||
def plugin = findFlutterPlugin(project.rootProject);
|
||||
|
||||
project.extensions.create("cargokit", CargoKitExtension)
|
||||
|
||||
if (plugin == null) {
|
||||
print("Flutter plugin not found, CargoKit plugin will not be applied.")
|
||||
return;
|
||||
}
|
||||
|
||||
def cargoBuildDir = "${project.buildDir}/build"
|
||||
|
||||
// Determine if the project is an application or library
|
||||
def isApplication = plugin.project.plugins.hasPlugin('com.android.application')
|
||||
def variants = isApplication ? plugin.project.android.applicationVariants : plugin.project.android.libraryVariants
|
||||
|
||||
variants.all { variant ->
|
||||
|
||||
final buildType = variant.buildType.name
|
||||
|
||||
def cargoOutputDir = "${project.buildDir}/jniLibs/${buildType}";
|
||||
def jniLibs = project.android.sourceSets.maybeCreate(buildType).jniLibs;
|
||||
jniLibs.srcDir(new File(cargoOutputDir))
|
||||
|
||||
def platforms = com.flutter.gradle.FlutterPluginUtils.getTargetPlatforms(project).collect()
|
||||
|
||||
// Same thing addFlutterDependencies does in flutter.gradle
|
||||
if (buildType == "debug") {
|
||||
platforms.add("android-x86")
|
||||
platforms.add("android-x64")
|
||||
}
|
||||
|
||||
// The task name depends on plugin properties, which are not available
|
||||
// at this point
|
||||
project.getGradle().afterProject {
|
||||
def taskName = "cargokitCargoBuild${project.cargokit.libname.capitalize()}${buildType.capitalize()}";
|
||||
|
||||
if (project.tasks.findByName(taskName)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (plugin.project.android.ndkVersion == null) {
|
||||
throw new GradleException("Please set 'android.ndkVersion' in 'app/build.gradle'.")
|
||||
}
|
||||
|
||||
def task = project.tasks.create(taskName, CargoKitBuildTask.class) {
|
||||
buildMode = variant.buildType.name
|
||||
buildDir = cargoBuildDir
|
||||
outputDir = cargoOutputDir
|
||||
ndkVersion = plugin.project.android.ndkVersion
|
||||
sdkDirectory = plugin.project.android.sdkDirectory
|
||||
minSdkVersion = plugin.project.android.defaultConfig.minSdkVersion.apiLevel as int
|
||||
compileSdkVersion = plugin.project.android.compileSdkVersion.substring(8) as int
|
||||
targetPlatforms = platforms
|
||||
pluginFile = CargoKitPlugin.file
|
||||
}
|
||||
def onTask = { newTask ->
|
||||
if (newTask.name == "merge${buildType.capitalize()}NativeLibs") {
|
||||
newTask.dependsOn task
|
||||
// Fix gradle 7.4.2 not picking up JNI library changes
|
||||
newTask.outputs.upToDateWhen { false }
|
||||
}
|
||||
}
|
||||
project.tasks.each onTask
|
||||
project.tasks.whenTaskAdded onTask
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
setlocal ENABLEDELAYEDEXPANSION
|
||||
|
||||
SET BASEDIR=%~dp0
|
||||
|
||||
if not exist "%CARGOKIT_TOOL_TEMP_DIR%" (
|
||||
mkdir "%CARGOKIT_TOOL_TEMP_DIR%"
|
||||
)
|
||||
cd /D "%CARGOKIT_TOOL_TEMP_DIR%"
|
||||
|
||||
SET BUILD_TOOL_PKG_DIR=%BASEDIR%build_tool
|
||||
SET DART=%FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart
|
||||
|
||||
set BUILD_TOOL_PKG_DIR_POSIX=%BUILD_TOOL_PKG_DIR:\=/%
|
||||
|
||||
(
|
||||
echo name: build_tool_runner
|
||||
echo version: 1.0.0
|
||||
echo publish_to: none
|
||||
echo.
|
||||
echo environment:
|
||||
echo sdk: '^>=3.0.0 ^<4.0.0'
|
||||
echo.
|
||||
echo dependencies:
|
||||
echo build_tool:
|
||||
echo path: %BUILD_TOOL_PKG_DIR_POSIX%
|
||||
) >pubspec.yaml
|
||||
|
||||
if not exist bin (
|
||||
mkdir bin
|
||||
)
|
||||
|
||||
(
|
||||
echo import 'package:build_tool/build_tool.dart' as build_tool;
|
||||
echo void main^(List^<String^> args^) ^{
|
||||
echo build_tool.runMain^(args^);
|
||||
echo ^}
|
||||
) >bin\build_tool_runner.dart
|
||||
|
||||
SET PRECOMPILED=bin\build_tool_runner.dill
|
||||
|
||||
REM To detect changes in package we compare output of DIR /s (recursive)
|
||||
set PREV_PACKAGE_INFO=.dart_tool\package_info.prev
|
||||
set CUR_PACKAGE_INFO=.dart_tool\package_info.cur
|
||||
|
||||
DIR "%BUILD_TOOL_PKG_DIR%" /s > "%CUR_PACKAGE_INFO%_orig"
|
||||
|
||||
REM Last line in dir output is free space on harddrive. That is bound to
|
||||
REM change between invocation so we need to remove it
|
||||
(
|
||||
Set "Line="
|
||||
For /F "UseBackQ Delims=" %%A In ("%CUR_PACKAGE_INFO%_orig") Do (
|
||||
SetLocal EnableDelayedExpansion
|
||||
If Defined Line Echo !Line!
|
||||
EndLocal
|
||||
Set "Line=%%A")
|
||||
) >"%CUR_PACKAGE_INFO%"
|
||||
DEL "%CUR_PACKAGE_INFO%_orig"
|
||||
|
||||
REM Compare current directory listing with previous
|
||||
FC /B "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" > nul 2>&1
|
||||
|
||||
If %ERRORLEVEL% neq 0 (
|
||||
REM Changed - copy current to previous and remove precompiled kernel
|
||||
if exist "%PREV_PACKAGE_INFO%" (
|
||||
DEL "%PREV_PACKAGE_INFO%"
|
||||
)
|
||||
MOVE /Y "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%"
|
||||
if exist "%PRECOMPILED%" (
|
||||
DEL "%PRECOMPILED%"
|
||||
)
|
||||
)
|
||||
|
||||
REM There is no CUR_PACKAGE_INFO it was renamed in previous step to %PREV_PACKAGE_INFO%
|
||||
REM which means we need to do pub get and precompile
|
||||
if not exist "%PRECOMPILED%" (
|
||||
echo Running pub get in "%cd%"
|
||||
"%DART%" pub get --no-precompile
|
||||
"%DART%" compile kernel bin/build_tool_runner.dart
|
||||
)
|
||||
|
||||
"%DART%" "%PRECOMPILED%" %*
|
||||
|
||||
REM 253 means invalid snapshot version.
|
||||
If %ERRORLEVEL% equ 253 (
|
||||
"%DART%" pub get --no-precompile
|
||||
"%DART%" compile kernel bin/build_tool_runner.dart
|
||||
"%DART%" "%PRECOMPILED%" %*
|
||||
)
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
set -e
|
||||
|
||||
BASEDIR=$(dirname "$0")
|
||||
|
||||
mkdir -p "$CARGOKIT_TOOL_TEMP_DIR"
|
||||
|
||||
cd "$CARGOKIT_TOOL_TEMP_DIR"
|
||||
|
||||
# Write a very simple bin package in temp folder that depends on build_tool package
|
||||
# from Cargokit. This is done to ensure that we don't pollute Cargokit folder
|
||||
# with .dart_tool contents.
|
||||
|
||||
BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool"
|
||||
|
||||
if [[ -z $FLUTTER_ROOT ]]; then # not defined
|
||||
DART=dart
|
||||
else
|
||||
DART="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart"
|
||||
fi
|
||||
|
||||
cat << EOF > "pubspec.yaml"
|
||||
name: build_tool_runner
|
||||
version: 1.0.0
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
build_tool:
|
||||
path: "$BUILD_TOOL_PKG_DIR"
|
||||
EOF
|
||||
|
||||
mkdir -p "bin"
|
||||
|
||||
cat << EOF > "bin/build_tool_runner.dart"
|
||||
import 'package:build_tool/build_tool.dart' as build_tool;
|
||||
void main(List<String> args) {
|
||||
build_tool.runMain(args);
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create alias for `shasum` if it does not exist and `sha1sum` exists
|
||||
if ! [ -x "$(command -v shasum)" ] && [ -x "$(command -v sha1sum)" ]; then
|
||||
shopt -s expand_aliases
|
||||
alias shasum="sha1sum"
|
||||
fi
|
||||
|
||||
# Dart run will not cache any package that has a path dependency, which
|
||||
# is the case for our build_tool_runner. So instead we precompile the package
|
||||
# ourselves.
|
||||
# To invalidate the cached kernel we use the hash of ls -LR of the build_tool
|
||||
# package directory. This should be good enough, as the build_tool package
|
||||
# itself is not meant to have any path dependencies.
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
PACKAGE_HASH=$(ls -lTR "$BUILD_TOOL_PKG_DIR" | shasum)
|
||||
else
|
||||
PACKAGE_HASH=$(ls -lR --full-time "$BUILD_TOOL_PKG_DIR" | shasum)
|
||||
fi
|
||||
|
||||
PACKAGE_HASH_FILE=".package_hash"
|
||||
|
||||
if [ -f "$PACKAGE_HASH_FILE" ]; then
|
||||
EXISTING_HASH=$(cat "$PACKAGE_HASH_FILE")
|
||||
if [ "$PACKAGE_HASH" != "$EXISTING_HASH" ]; then
|
||||
rm "$PACKAGE_HASH_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run pub get if needed.
|
||||
if [ ! -f "$PACKAGE_HASH_FILE" ]; then
|
||||
"$DART" pub get --no-precompile
|
||||
"$DART" compile kernel bin/build_tool_runner.dart
|
||||
echo "$PACKAGE_HASH" > "$PACKAGE_HASH_FILE"
|
||||
fi
|
||||
|
||||
# Rebuild the tool if it was deleted by Android Studio
|
||||
if [ ! -f "bin/build_tool_runner.dill" ]; then
|
||||
"$DART" compile kernel bin/build_tool_runner.dart
|
||||
fi
|
||||
|
||||
set +e
|
||||
|
||||
"$DART" bin/build_tool_runner.dill "$@"
|
||||
|
||||
exit_code=$?
|
||||
|
||||
# 253 means invalid snapshot version.
|
||||
if [ $exit_code == 253 ]; then
|
||||
"$DART" pub get --no-precompile
|
||||
"$DART" compile kernel bin/build_tool_runner.dart
|
||||
"$DART" bin/build_tool_runner.dill "$@"
|
||||
exit_code=$?
|
||||
fi
|
||||
|
||||
exit $exit_code
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Relative import to be able to reuse the C sources.
|
||||
// See the comment in ../{projectName}}.podspec for more information.
|
||||
@@ -1,37 +0,0 @@
|
||||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint tor.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'tor'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'A new Flutter FFI plugin project.'
|
||||
s.description = <<-DESC
|
||||
A new Flutter FFI plugin project.
|
||||
DESC
|
||||
s.homepage = 'http://example.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Your Company' => 'email@example.com' }
|
||||
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'Flutter'
|
||||
s.platform = :ios, '9.0'
|
||||
|
||||
s.script_phase = {
|
||||
:name => 'Build Rust library',
|
||||
# First argument is relative path to the `rust` folder, second is name of rust library
|
||||
:script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust tor',
|
||||
:execution_position => :before_compile,
|
||||
:input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'],
|
||||
# Let XCode know that the static library referenced in -force_load below is
|
||||
# created by this build step.
|
||||
:output_files => ["${BUILT_PRODUCTS_DIR}/libtor.a"],
|
||||
}
|
||||
s.pod_target_xcconfig = {
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
# Flutter.framework does not contain a i386 slice.
|
||||
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
|
||||
'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libtor.a',
|
||||
}
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,12 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2022 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
generate:
|
||||
cargo build --manifest-path rust/Cargo.toml && \
|
||||
dart run ffigen
|
||||
|
||||
format:
|
||||
cargo fmt --manifest-path rust/Cargo.toml && \
|
||||
dart format . && \
|
||||
flutter analyze
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,558 +0,0 @@
|
||||
// AUTO GENERATED FILE, DO NOT EDIT.
|
||||
//
|
||||
// Generated by `package:ffigen`.
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
import 'dart:ffi' as ffi;
|
||||
|
||||
class NativeLibrary {
|
||||
/// Holds the symbol lookup function.
|
||||
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
||||
_lookup;
|
||||
|
||||
/// The symbols are looked up in [dynamicLibrary].
|
||||
NativeLibrary(ffi.DynamicLibrary dynamicLibrary)
|
||||
: _lookup = dynamicLibrary.lookup;
|
||||
|
||||
/// The symbols are looked up with [lookup].
|
||||
NativeLibrary.fromLookup(
|
||||
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName) lookup,
|
||||
) : _lookup = lookup;
|
||||
|
||||
Tor tor_start(
|
||||
int socks_port,
|
||||
ffi.Pointer<ffi.Char> state_dir,
|
||||
ffi.Pointer<ffi.Char> cache_dir,
|
||||
int obfs4_port,
|
||||
int snowflake_port,
|
||||
ffi.Pointer<ffi.Char> bridge_lines,
|
||||
) {
|
||||
return _tor_start(
|
||||
socks_port,
|
||||
state_dir,
|
||||
cache_dir,
|
||||
obfs4_port,
|
||||
snowflake_port,
|
||||
bridge_lines,
|
||||
);
|
||||
}
|
||||
|
||||
late final _tor_startPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
Tor Function(
|
||||
ffi.Uint16,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Uint16,
|
||||
ffi.Uint16,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
)
|
||||
>
|
||||
>('tor_start');
|
||||
late final _tor_start = _tor_startPtr
|
||||
.asFunction<
|
||||
Tor Function(
|
||||
int,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
int,
|
||||
int,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
)
|
||||
>();
|
||||
|
||||
bool tor_reconfigure(
|
||||
ffi.Pointer<ffi.Void> client,
|
||||
ffi.Pointer<ffi.Char> state_dir,
|
||||
ffi.Pointer<ffi.Char> cache_dir,
|
||||
int obfs4_port,
|
||||
int snowflake_port,
|
||||
ffi.Pointer<ffi.Char> bridge_lines,
|
||||
) {
|
||||
return _tor_reconfigure(
|
||||
client,
|
||||
state_dir,
|
||||
cache_dir,
|
||||
obfs4_port,
|
||||
snowflake_port,
|
||||
bridge_lines,
|
||||
);
|
||||
}
|
||||
|
||||
late final _tor_reconfigurePtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Bool Function(
|
||||
ffi.Pointer<ffi.Void>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Uint16,
|
||||
ffi.Uint16,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
)
|
||||
>
|
||||
>('tor_reconfigure');
|
||||
late final _tor_reconfigure = _tor_reconfigurePtr
|
||||
.asFunction<
|
||||
bool Function(
|
||||
ffi.Pointer<ffi.Void>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
int,
|
||||
int,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
)
|
||||
>();
|
||||
|
||||
bool tor_client_bootstrap(ffi.Pointer<ffi.Void> client) {
|
||||
return _tor_client_bootstrap(client);
|
||||
}
|
||||
|
||||
late final _tor_client_bootstrapPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Bool Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'tor_client_bootstrap',
|
||||
);
|
||||
late final _tor_client_bootstrap = _tor_client_bootstrapPtr
|
||||
.asFunction<bool Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void tor_client_set_dormant(ffi.Pointer<ffi.Void> client, bool soft_mode) {
|
||||
return _tor_client_set_dormant(client, soft_mode);
|
||||
}
|
||||
|
||||
late final _tor_client_set_dormantPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.Bool)>
|
||||
>('tor_client_set_dormant');
|
||||
late final _tor_client_set_dormant = _tor_client_set_dormantPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>, bool)>();
|
||||
|
||||
void tor_proxy_stop(ffi.Pointer<ffi.Void> proxy) {
|
||||
return _tor_proxy_stop(proxy);
|
||||
}
|
||||
|
||||
late final _tor_proxy_stopPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'tor_proxy_stop',
|
||||
);
|
||||
late final _tor_proxy_stop = _tor_proxy_stopPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void tor_hello() {
|
||||
return _tor_hello();
|
||||
}
|
||||
|
||||
late final _tor_helloPtr = _lookup<ffi.NativeFunction<ffi.Void Function()>>(
|
||||
'tor_hello',
|
||||
);
|
||||
late final _tor_hello = _tor_helloPtr.asFunction<void Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Char> tor_last_error_message() {
|
||||
return _tor_last_error_message();
|
||||
}
|
||||
|
||||
late final _tor_last_error_messagePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
|
||||
'tor_last_error_message',
|
||||
);
|
||||
late final _tor_last_error_message = _tor_last_error_messagePtr
|
||||
.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
int tor_get_nofile_limit() {
|
||||
return _tor_get_nofile_limit();
|
||||
}
|
||||
|
||||
late final _tor_get_nofile_limitPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Uint64 Function()>>(
|
||||
'tor_get_nofile_limit',
|
||||
);
|
||||
late final _tor_get_nofile_limit = _tor_get_nofile_limitPtr
|
||||
.asFunction<int Function()>();
|
||||
|
||||
int tor_set_nofile_limit(int limit) {
|
||||
return _tor_set_nofile_limit(limit);
|
||||
}
|
||||
|
||||
late final _tor_set_nofile_limitPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Uint64 Function(ffi.Uint64)>>(
|
||||
'tor_set_nofile_limit',
|
||||
);
|
||||
late final _tor_set_nofile_limit = _tor_set_nofile_limitPtr
|
||||
.asFunction<int Function(int)>();
|
||||
}
|
||||
|
||||
typedef __u_char = ffi.UnsignedChar;
|
||||
typedef Dart__u_char = int;
|
||||
typedef __u_short = ffi.UnsignedShort;
|
||||
typedef Dart__u_short = int;
|
||||
typedef __u_int = ffi.UnsignedInt;
|
||||
typedef Dart__u_int = int;
|
||||
typedef __u_long = ffi.UnsignedLong;
|
||||
typedef Dart__u_long = int;
|
||||
typedef __int8_t = ffi.SignedChar;
|
||||
typedef Dart__int8_t = int;
|
||||
typedef __uint8_t = ffi.UnsignedChar;
|
||||
typedef Dart__uint8_t = int;
|
||||
typedef __int16_t = ffi.Short;
|
||||
typedef Dart__int16_t = int;
|
||||
typedef __uint16_t = ffi.UnsignedShort;
|
||||
typedef Dart__uint16_t = int;
|
||||
typedef __int32_t = ffi.Int;
|
||||
typedef Dart__int32_t = int;
|
||||
typedef __uint32_t = ffi.UnsignedInt;
|
||||
typedef Dart__uint32_t = int;
|
||||
typedef __int64_t = ffi.Long;
|
||||
typedef Dart__int64_t = int;
|
||||
typedef __uint64_t = ffi.UnsignedLong;
|
||||
typedef Dart__uint64_t = int;
|
||||
typedef __int_least8_t = __int8_t;
|
||||
typedef __uint_least8_t = __uint8_t;
|
||||
typedef __int_least16_t = __int16_t;
|
||||
typedef __uint_least16_t = __uint16_t;
|
||||
typedef __int_least32_t = __int32_t;
|
||||
typedef __uint_least32_t = __uint32_t;
|
||||
typedef __int_least64_t = __int64_t;
|
||||
typedef __uint_least64_t = __uint64_t;
|
||||
typedef __quad_t = ffi.Long;
|
||||
typedef Dart__quad_t = int;
|
||||
typedef __u_quad_t = ffi.UnsignedLong;
|
||||
typedef Dart__u_quad_t = int;
|
||||
typedef __intmax_t = ffi.Long;
|
||||
typedef Dart__intmax_t = int;
|
||||
typedef __uintmax_t = ffi.UnsignedLong;
|
||||
typedef Dart__uintmax_t = int;
|
||||
typedef __dev_t = ffi.UnsignedLong;
|
||||
typedef Dart__dev_t = int;
|
||||
typedef __uid_t = ffi.UnsignedInt;
|
||||
typedef Dart__uid_t = int;
|
||||
typedef __gid_t = ffi.UnsignedInt;
|
||||
typedef Dart__gid_t = int;
|
||||
typedef __ino_t = ffi.UnsignedLong;
|
||||
typedef Dart__ino_t = int;
|
||||
typedef __ino64_t = ffi.UnsignedLong;
|
||||
typedef Dart__ino64_t = int;
|
||||
typedef __mode_t = ffi.UnsignedInt;
|
||||
typedef Dart__mode_t = int;
|
||||
typedef __nlink_t = ffi.UnsignedLong;
|
||||
typedef Dart__nlink_t = int;
|
||||
typedef __off_t = ffi.Long;
|
||||
typedef Dart__off_t = int;
|
||||
typedef __off64_t = ffi.Long;
|
||||
typedef Dart__off64_t = int;
|
||||
typedef __pid_t = ffi.Int;
|
||||
typedef Dart__pid_t = int;
|
||||
|
||||
final class __fsid_t extends ffi.Struct {
|
||||
@ffi.Array.multi([2])
|
||||
external ffi.Array<ffi.Int> __val;
|
||||
}
|
||||
|
||||
typedef __clock_t = ffi.Long;
|
||||
typedef Dart__clock_t = int;
|
||||
typedef __rlim_t = ffi.UnsignedLong;
|
||||
typedef Dart__rlim_t = int;
|
||||
typedef __rlim64_t = ffi.UnsignedLong;
|
||||
typedef Dart__rlim64_t = int;
|
||||
typedef __id_t = ffi.UnsignedInt;
|
||||
typedef Dart__id_t = int;
|
||||
typedef __time_t = ffi.Long;
|
||||
typedef Dart__time_t = int;
|
||||
typedef __useconds_t = ffi.UnsignedInt;
|
||||
typedef Dart__useconds_t = int;
|
||||
typedef __suseconds_t = ffi.Long;
|
||||
typedef Dart__suseconds_t = int;
|
||||
typedef __suseconds64_t = ffi.Long;
|
||||
typedef Dart__suseconds64_t = int;
|
||||
typedef __daddr_t = ffi.Int;
|
||||
typedef Dart__daddr_t = int;
|
||||
typedef __key_t = ffi.Int;
|
||||
typedef Dart__key_t = int;
|
||||
typedef __clockid_t = ffi.Int;
|
||||
typedef Dart__clockid_t = int;
|
||||
typedef __timer_t = ffi.Pointer<ffi.Void>;
|
||||
typedef __blksize_t = ffi.Long;
|
||||
typedef Dart__blksize_t = int;
|
||||
typedef __blkcnt_t = ffi.Long;
|
||||
typedef Dart__blkcnt_t = int;
|
||||
typedef __blkcnt64_t = ffi.Long;
|
||||
typedef Dart__blkcnt64_t = int;
|
||||
typedef __fsblkcnt_t = ffi.UnsignedLong;
|
||||
typedef Dart__fsblkcnt_t = int;
|
||||
typedef __fsblkcnt64_t = ffi.UnsignedLong;
|
||||
typedef Dart__fsblkcnt64_t = int;
|
||||
typedef __fsfilcnt_t = ffi.UnsignedLong;
|
||||
typedef Dart__fsfilcnt_t = int;
|
||||
typedef __fsfilcnt64_t = ffi.UnsignedLong;
|
||||
typedef Dart__fsfilcnt64_t = int;
|
||||
typedef __fsword_t = ffi.Long;
|
||||
typedef Dart__fsword_t = int;
|
||||
typedef __ssize_t = ffi.Long;
|
||||
typedef Dart__ssize_t = int;
|
||||
typedef __syscall_slong_t = ffi.Long;
|
||||
typedef Dart__syscall_slong_t = int;
|
||||
typedef __syscall_ulong_t = ffi.UnsignedLong;
|
||||
typedef Dart__syscall_ulong_t = int;
|
||||
typedef __loff_t = __off64_t;
|
||||
typedef __caddr_t = ffi.Pointer<ffi.Char>;
|
||||
typedef __intptr_t = ffi.Long;
|
||||
typedef Dart__intptr_t = int;
|
||||
typedef __socklen_t = ffi.UnsignedInt;
|
||||
typedef Dart__socklen_t = int;
|
||||
typedef __sig_atomic_t = ffi.Int;
|
||||
typedef Dart__sig_atomic_t = int;
|
||||
typedef int_least8_t = __int_least8_t;
|
||||
typedef int_least16_t = __int_least16_t;
|
||||
typedef int_least32_t = __int_least32_t;
|
||||
typedef int_least64_t = __int_least64_t;
|
||||
typedef uint_least8_t = __uint_least8_t;
|
||||
typedef uint_least16_t = __uint_least16_t;
|
||||
typedef uint_least32_t = __uint_least32_t;
|
||||
typedef uint_least64_t = __uint_least64_t;
|
||||
typedef int_fast8_t = ffi.SignedChar;
|
||||
typedef Dartint_fast8_t = int;
|
||||
typedef int_fast16_t = ffi.Long;
|
||||
typedef Dartint_fast16_t = int;
|
||||
typedef int_fast32_t = ffi.Long;
|
||||
typedef Dartint_fast32_t = int;
|
||||
typedef int_fast64_t = ffi.Long;
|
||||
typedef Dartint_fast64_t = int;
|
||||
typedef uint_fast8_t = ffi.UnsignedChar;
|
||||
typedef Dartuint_fast8_t = int;
|
||||
typedef uint_fast16_t = ffi.UnsignedLong;
|
||||
typedef Dartuint_fast16_t = int;
|
||||
typedef uint_fast32_t = ffi.UnsignedLong;
|
||||
typedef Dartuint_fast32_t = int;
|
||||
typedef uint_fast64_t = ffi.UnsignedLong;
|
||||
typedef Dartuint_fast64_t = int;
|
||||
typedef intmax_t = __intmax_t;
|
||||
typedef uintmax_t = __uintmax_t;
|
||||
typedef _Float32 = ffi.Float;
|
||||
typedef Dart_Float32 = double;
|
||||
typedef _Float64 = ffi.Double;
|
||||
typedef Dart_Float64 = double;
|
||||
typedef _Float32x = ffi.Double;
|
||||
typedef Dart_Float32x = double;
|
||||
typedef u_char = __u_char;
|
||||
typedef u_short = __u_short;
|
||||
typedef u_int = __u_int;
|
||||
typedef u_long = __u_long;
|
||||
typedef quad_t = __quad_t;
|
||||
typedef u_quad_t = __u_quad_t;
|
||||
typedef fsid_t = __fsid_t;
|
||||
typedef loff_t = __loff_t;
|
||||
typedef ino_t = __ino_t;
|
||||
typedef dev_t = __dev_t;
|
||||
typedef gid_t = __gid_t;
|
||||
typedef mode_t = __mode_t;
|
||||
typedef nlink_t = __nlink_t;
|
||||
typedef uid_t = __uid_t;
|
||||
typedef off_t = __off_t;
|
||||
typedef pid_t = __pid_t;
|
||||
typedef id_t = __id_t;
|
||||
typedef ssize_t = __ssize_t;
|
||||
typedef daddr_t = __daddr_t;
|
||||
typedef caddr_t = __caddr_t;
|
||||
typedef key_t = __key_t;
|
||||
typedef clock_t = __clock_t;
|
||||
typedef clockid_t = __clockid_t;
|
||||
typedef time_t = __time_t;
|
||||
typedef timer_t = __timer_t;
|
||||
typedef ulong = ffi.UnsignedLong;
|
||||
typedef Dartulong = int;
|
||||
typedef ushort = ffi.UnsignedShort;
|
||||
typedef Dartushort = int;
|
||||
typedef uint = ffi.UnsignedInt;
|
||||
typedef Dartuint = int;
|
||||
typedef u_int8_t = __uint8_t;
|
||||
typedef u_int16_t = __uint16_t;
|
||||
typedef u_int32_t = __uint32_t;
|
||||
typedef u_int64_t = __uint64_t;
|
||||
typedef register_t = ffi.Long;
|
||||
typedef Dartregister_t = int;
|
||||
|
||||
final class __sigset_t extends ffi.Struct {
|
||||
@ffi.Array.multi([16])
|
||||
external ffi.Array<ffi.UnsignedLong> __val;
|
||||
}
|
||||
|
||||
typedef sigset_t = __sigset_t;
|
||||
typedef suseconds_t = __suseconds_t;
|
||||
typedef __fd_mask = ffi.Long;
|
||||
typedef Dart__fd_mask = int;
|
||||
typedef fd_mask = __fd_mask;
|
||||
typedef blksize_t = __blksize_t;
|
||||
typedef blkcnt_t = __blkcnt_t;
|
||||
typedef fsblkcnt_t = __fsblkcnt_t;
|
||||
typedef fsfilcnt_t = __fsfilcnt_t;
|
||||
|
||||
final class __pthread_internal_list extends ffi.Struct {
|
||||
external ffi.Pointer<__pthread_internal_list> __prev;
|
||||
|
||||
external ffi.Pointer<__pthread_internal_list> __next;
|
||||
}
|
||||
|
||||
typedef __pthread_list_t = __pthread_internal_list;
|
||||
|
||||
final class __pthread_internal_slist extends ffi.Struct {
|
||||
external ffi.Pointer<__pthread_internal_slist> __next;
|
||||
}
|
||||
|
||||
typedef __pthread_slist_t = __pthread_internal_slist;
|
||||
typedef __tss_t = ffi.UnsignedInt;
|
||||
typedef Dart__tss_t = int;
|
||||
typedef __thrd_t = ffi.UnsignedLong;
|
||||
typedef Dart__thrd_t = int;
|
||||
typedef pthread_t = ffi.UnsignedLong;
|
||||
typedef Dartpthread_t = int;
|
||||
typedef pthread_key_t = ffi.UnsignedInt;
|
||||
typedef Dartpthread_key_t = int;
|
||||
typedef pthread_once_t = ffi.Int;
|
||||
typedef Dartpthread_once_t = int;
|
||||
typedef __compar_fn_tFunction =
|
||||
ffi.Int Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>);
|
||||
typedef Dart__compar_fn_tFunction =
|
||||
int Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>);
|
||||
typedef __compar_fn_t = ffi.Pointer<ffi.NativeFunction<__compar_fn_tFunction>>;
|
||||
|
||||
final class Tor extends ffi.Struct {
|
||||
external ffi.Pointer<ffi.Void> client;
|
||||
|
||||
external ffi.Pointer<ffi.Void> proxy;
|
||||
}
|
||||
|
||||
const int true$ = 1;
|
||||
|
||||
const int false$ = 0;
|
||||
|
||||
const int INT8_MIN = -128;
|
||||
|
||||
const int INT16_MIN = -32768;
|
||||
|
||||
const int INT32_MIN = -2147483648;
|
||||
|
||||
const int INT64_MIN = -9223372036854775808;
|
||||
|
||||
const int INT8_MAX = 127;
|
||||
|
||||
const int INT16_MAX = 32767;
|
||||
|
||||
const int INT32_MAX = 2147483647;
|
||||
|
||||
const int INT64_MAX = 9223372036854775807;
|
||||
|
||||
const int UINT8_MAX = 255;
|
||||
|
||||
const int UINT16_MAX = 65535;
|
||||
|
||||
const int UINT32_MAX = 4294967295;
|
||||
|
||||
const int UINT64_MAX = -1;
|
||||
|
||||
const int INT_LEAST8_MIN = -128;
|
||||
|
||||
const int INT_LEAST16_MIN = -32768;
|
||||
|
||||
const int INT_LEAST32_MIN = -2147483648;
|
||||
|
||||
const int INT_LEAST64_MIN = -9223372036854775808;
|
||||
|
||||
const int INT_LEAST8_MAX = 127;
|
||||
|
||||
const int INT_LEAST16_MAX = 32767;
|
||||
|
||||
const int INT_LEAST32_MAX = 2147483647;
|
||||
|
||||
const int INT_LEAST64_MAX = 9223372036854775807;
|
||||
|
||||
const int UINT_LEAST8_MAX = 255;
|
||||
|
||||
const int UINT_LEAST16_MAX = 65535;
|
||||
|
||||
const int UINT_LEAST32_MAX = 4294967295;
|
||||
|
||||
const int UINT_LEAST64_MAX = -1;
|
||||
|
||||
const int INT_FAST8_MIN = -128;
|
||||
|
||||
const int INT_FAST16_MIN = -9223372036854775808;
|
||||
|
||||
const int INT_FAST32_MIN = -9223372036854775808;
|
||||
|
||||
const int INT_FAST64_MIN = -9223372036854775808;
|
||||
|
||||
const int INT_FAST8_MAX = 127;
|
||||
|
||||
const int INT_FAST16_MAX = 9223372036854775807;
|
||||
|
||||
const int INT_FAST32_MAX = 9223372036854775807;
|
||||
|
||||
const int INT_FAST64_MAX = 9223372036854775807;
|
||||
|
||||
const int UINT_FAST8_MAX = 255;
|
||||
|
||||
const int UINT_FAST16_MAX = -1;
|
||||
|
||||
const int UINT_FAST32_MAX = -1;
|
||||
|
||||
const int UINT_FAST64_MAX = -1;
|
||||
|
||||
const int INTPTR_MIN = -9223372036854775808;
|
||||
|
||||
const int INTPTR_MAX = 9223372036854775807;
|
||||
|
||||
const int UINTPTR_MAX = -1;
|
||||
|
||||
const int INTMAX_MIN = -9223372036854775808;
|
||||
|
||||
const int INTMAX_MAX = 9223372036854775807;
|
||||
|
||||
const int UINTMAX_MAX = -1;
|
||||
|
||||
const int PTRDIFF_MIN = -9223372036854775808;
|
||||
|
||||
const int PTRDIFF_MAX = 9223372036854775807;
|
||||
|
||||
const int SIG_ATOMIC_MIN = -2147483648;
|
||||
|
||||
const int SIG_ATOMIC_MAX = 2147483647;
|
||||
|
||||
const int SIZE_MAX = -1;
|
||||
|
||||
const int WCHAR_MIN = -2147483648;
|
||||
|
||||
const int WCHAR_MAX = 2147483647;
|
||||
|
||||
const int WINT_MIN = 0;
|
||||
|
||||
const int WINT_MAX = 4294967295;
|
||||
|
||||
const int NULL = 0;
|
||||
|
||||
const int WNOHANG = 1;
|
||||
|
||||
const int WUNTRACED = 2;
|
||||
|
||||
const int WSTOPPED = 2;
|
||||
|
||||
const int WEXITED = 4;
|
||||
|
||||
const int WCONTINUED = 8;
|
||||
|
||||
const int WNOWAIT = 16777216;
|
||||
|
||||
const int RAND_MAX = 2147483647;
|
||||
|
||||
const int EXIT_FAILURE = 1;
|
||||
|
||||
const int EXIT_SUCCESS = 0;
|
||||
|
||||
const int LITTLE_ENDIAN = 1234;
|
||||
|
||||
const int BIG_ENDIAN = 4321;
|
||||
|
||||
const int PDP_ENDIAN = 3412;
|
||||
|
||||
const int BYTE_ORDER = 1234;
|
||||
|
||||
const int FD_SETSIZE = 1024;
|
||||
|
||||
const int NFDBITS = 64;
|
||||
@@ -1,322 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Foundation Devices Inc.
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||
import 'package:tor/src/generated_bindings.dart' as rust;
|
||||
|
||||
DynamicLibrary load(String name) {
|
||||
if (Platform.isAndroid || Platform.isLinux) {
|
||||
return DynamicLibrary.open('lib$name.so');
|
||||
} else if (Platform.isIOS || Platform.isMacOS) {
|
||||
return DynamicLibrary.open('$name.framework/$name');
|
||||
} else if (Platform.isWindows) {
|
||||
return DynamicLibrary.open('$name.dll');
|
||||
} else {
|
||||
throw NotSupportedPlatform('${Platform.operatingSystem} is not supported!');
|
||||
}
|
||||
}
|
||||
|
||||
class CouldntBootstrapDirectory implements Exception {
|
||||
String? rustError;
|
||||
|
||||
CouldntBootstrapDirectory({this.rustError});
|
||||
}
|
||||
|
||||
class NotSupportedPlatform implements Exception {
|
||||
String reason;
|
||||
|
||||
NotSupportedPlatform(this.reason);
|
||||
}
|
||||
|
||||
class ClientNotActive implements Exception {}
|
||||
|
||||
class Tor {
|
||||
static const String libName = "tor";
|
||||
static late DynamicLibrary _lib;
|
||||
|
||||
Pointer<Void> _clientPtr = nullptr;
|
||||
Pointer<Void> _proxyPtr = nullptr;
|
||||
|
||||
/// Flag to indicate that Tor client and proxy have started. Traffic is routed through the proxy only if it is also [enabled].
|
||||
bool get started => _proxyPort > -1;
|
||||
|
||||
bool get hasClient => _clientPtr != nullptr;
|
||||
|
||||
/// Flag to indicate that traffic should flow through the proxy.
|
||||
bool _enabled = false;
|
||||
|
||||
/// Getter for the enabled flag.
|
||||
bool get enabled => _enabled;
|
||||
|
||||
/// Flag to indicate that a Tor circuit is thought to have been established
|
||||
/// (true means that Tor has bootstrapped).
|
||||
bool get bootstrapped => _bootstrapped;
|
||||
|
||||
/// Getter for the bootstrapped flag.
|
||||
bool _bootstrapped = false;
|
||||
|
||||
/// A stream of Tor events.
|
||||
///
|
||||
/// This stream broadcast just the port for now (-1 if circuit not established or proxy not enabled)
|
||||
final StreamController events = StreamController.broadcast();
|
||||
|
||||
/// Getter for the proxy port.
|
||||
///
|
||||
/// Returns -1 if Tor is not enabled or if the circuit is not established.
|
||||
///
|
||||
/// Returns the proxy port if Tor is enabled and the circuit is established.
|
||||
///
|
||||
/// This is the port that should be used for all requests.
|
||||
int get port {
|
||||
if (!_enabled) {
|
||||
return -1;
|
||||
}
|
||||
return _proxyPort;
|
||||
}
|
||||
|
||||
/// The proxy port.
|
||||
int _proxyPort = -1;
|
||||
|
||||
/// Singleton instance of the Tor class.
|
||||
static final Tor _instance = Tor._internal();
|
||||
|
||||
/// Getter for the singleton instance of the Tor class.
|
||||
static Tor get instance => _instance;
|
||||
|
||||
/// Initialize the Tor ffi lib instance if it hasn't already been set. Nothing
|
||||
/// changes if _tor is already been set.
|
||||
///
|
||||
/// Returns a Future that completes when the Tor service has started.
|
||||
///
|
||||
/// Throws an exception if the Tor service fails to start.
|
||||
static Tor init({bool enabled = true}) {
|
||||
final singleton = Tor._instance;
|
||||
singleton._enabled = enabled;
|
||||
return singleton;
|
||||
}
|
||||
|
||||
/// Private constructor for the Tor class.
|
||||
Tor._internal() {
|
||||
_lib = load(libName);
|
||||
|
||||
if (kDebugMode) {
|
||||
print("Instance of Tor created!");
|
||||
}
|
||||
}
|
||||
|
||||
void broadcastState() {
|
||||
events.add(port);
|
||||
}
|
||||
|
||||
Future<int> _getRandomUnusedPort({List<int> excluded = const []}) async {
|
||||
final random = Random.secure();
|
||||
int potentialPort = 0;
|
||||
|
||||
retry:
|
||||
while (potentialPort <= 0 || excluded.contains(potentialPort)) {
|
||||
potentialPort = random.nextInt(65535);
|
||||
try {
|
||||
final socket = await ServerSocket.bind("0.0.0.0", potentialPort);
|
||||
await socket.close();
|
||||
return potentialPort;
|
||||
} catch (_) {
|
||||
continue retry;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Start the Tor service.
|
||||
///
|
||||
/// This will start the Tor service and establish a Tor circuit.
|
||||
///
|
||||
/// Throws an exception if the Tor service fails to start.
|
||||
///
|
||||
/// Returns a Future that completes when the Tor service has started.
|
||||
Future<void> start({
|
||||
int? obfs4Port,
|
||||
int? snowflakePort,
|
||||
String? bridgeLines,
|
||||
}) async {
|
||||
broadcastState();
|
||||
|
||||
// Set the state and cache directories.
|
||||
final appCacheDir = await path_provider.getApplicationCacheDirectory();
|
||||
final stateDir = await Directory('${appCacheDir.path}/tor_state').create();
|
||||
final cacheDir = await Directory('${appCacheDir.path}/tor_cache').create();
|
||||
|
||||
// Generate a random port.
|
||||
final newPort = await _getRandomUnusedPort();
|
||||
|
||||
// Start the Tor service in an isolate.
|
||||
final tor = await Isolate.run(() {
|
||||
// Load the Tor library.
|
||||
final lib = rust.NativeLibrary(load(libName));
|
||||
|
||||
// Start the Tor service.
|
||||
final tor = lib.tor_start(
|
||||
newPort,
|
||||
stateDir.path.toNativeUtf8() as Pointer<Char>,
|
||||
cacheDir.path.toNativeUtf8() as Pointer<Char>,
|
||||
obfs4Port ?? -1,
|
||||
snowflakePort ?? -1,
|
||||
bridgeLines?.toNativeUtf8() as Pointer<Char>? ?? nullptr,
|
||||
);
|
||||
|
||||
// Throw an exception if the Tor service fails to start.
|
||||
if (tor.client == nullptr) {
|
||||
throwRustException(lib);
|
||||
}
|
||||
|
||||
return tor;
|
||||
});
|
||||
|
||||
// Set the client pointer and started flag.
|
||||
_clientPtr = Pointer.fromAddress(tor.client.address);
|
||||
_proxyPtr = Pointer.fromAddress(tor.proxy.address);
|
||||
|
||||
// Bootstrap the Tor service.
|
||||
bootstrap();
|
||||
|
||||
// Set the proxy port.
|
||||
_proxyPort = newPort;
|
||||
broadcastState();
|
||||
}
|
||||
|
||||
Future<void> reconfigure({
|
||||
int? obfs4Port,
|
||||
int? snowflakePort,
|
||||
String? bridgeLines,
|
||||
}) async {
|
||||
final lib = rust.NativeLibrary(_lib);
|
||||
|
||||
// Set the state and cache directories.
|
||||
final Directory appSupportDir = await path_provider
|
||||
.getApplicationSupportDirectory();
|
||||
final stateDir = await Directory(
|
||||
'${appSupportDir.path}/tor_state',
|
||||
).create();
|
||||
final cacheDir = await Directory(
|
||||
'${appSupportDir.path}/tor_cache',
|
||||
).create();
|
||||
|
||||
final reconfigured = lib.tor_reconfigure(
|
||||
_clientPtr,
|
||||
stateDir.path.toNativeUtf8() as Pointer<Char>,
|
||||
cacheDir.path.toNativeUtf8() as Pointer<Char>,
|
||||
obfs4Port ?? -1,
|
||||
snowflakePort ?? -1,
|
||||
bridgeLines?.toNativeUtf8() as Pointer<Char>? ?? nullptr,
|
||||
);
|
||||
|
||||
if (!reconfigured) {
|
||||
throwRustException(lib);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bootstrap the Tor service.
|
||||
///
|
||||
/// This will bootstrap the Tor service and establish a Tor circuit. This
|
||||
/// function should only be called after the Tor service has been started.
|
||||
///
|
||||
/// This function will block until the Tor service has bootstrapped.
|
||||
///
|
||||
/// Throws an exception if the Tor service fails to bootstrap.
|
||||
///
|
||||
/// Returns void.
|
||||
void bootstrap() {
|
||||
// Load the Tor library.
|
||||
final lib = rust.NativeLibrary(_lib);
|
||||
|
||||
// Bootstrap the Tor service.
|
||||
_bootstrapped = lib.tor_client_bootstrap(_clientPtr);
|
||||
|
||||
// Throw an exception if the Tor service fails to bootstrap.
|
||||
if (!bootstrapped) {
|
||||
throwRustException(lib);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prevent traffic flowing through the proxy
|
||||
void disable() {
|
||||
stop();
|
||||
|
||||
_enabled = false;
|
||||
broadcastState();
|
||||
}
|
||||
|
||||
/// Stops the proxy
|
||||
void stop() {
|
||||
final lib = rust.NativeLibrary(_lib);
|
||||
if (_proxyPtr != nullptr) {
|
||||
lib.tor_proxy_stop(_proxyPtr);
|
||||
_proxyPtr = nullptr;
|
||||
|
||||
_bootstrapped = false;
|
||||
_proxyPort = -1;
|
||||
|
||||
broadcastState();
|
||||
}
|
||||
}
|
||||
|
||||
void setClientDormant(bool dormant) {
|
||||
if (_clientPtr == nullptr || !started || !bootstrapped) {
|
||||
throw ClientNotActive();
|
||||
}
|
||||
|
||||
final lib = rust.NativeLibrary(_lib);
|
||||
lib.tor_client_set_dormant(_clientPtr, dormant);
|
||||
}
|
||||
|
||||
Future<void> isReady() async {
|
||||
return await Future.doWhile(
|
||||
() => Future.delayed(const Duration(seconds: 1)).then((_) {
|
||||
// We are waiting and making absolutely no request unless:
|
||||
// Tor is disabled
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...or Tor circuit is established
|
||||
if (bootstrapped) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This way we avoid making clearnet req's while Tor is initialising
|
||||
return true;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static void throwRustException(rust.NativeLibrary lib) {
|
||||
final String rustError = lib
|
||||
.tor_last_error_message()
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
|
||||
throw _getRustException(rustError);
|
||||
}
|
||||
|
||||
static Exception _getRustException(String rustError) {
|
||||
if (rustError.contains('Unable to bootstrap a working directory')) {
|
||||
return CouldntBootstrapDirectory(rustError: rustError);
|
||||
} else {
|
||||
return Exception(rustError);
|
||||
}
|
||||
}
|
||||
|
||||
void hello() {
|
||||
rust.NativeLibrary(_lib).tor_hello();
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export 'src/tor.dart';
|
||||
@@ -1,27 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# The Flutter tooling requires that developers have CMake 3.10 or later
|
||||
# installed. You should not increase this version, as doing so will cause
|
||||
# the plugin to fail to compile for some customers of the plugin.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Project-level configuration.
|
||||
set(PROJECT_NAME "tor")
|
||||
project(${PROJECT_NAME} LANGUAGES CXX)
|
||||
|
||||
# Invoke the build for native code shared with the other target platforms.
|
||||
# This can be changed to accommodate different builds.
|
||||
include("../cargokit/cmake/cargokit.cmake")
|
||||
apply_cargokit(${PROJECT_NAME} ../rust tor "")
|
||||
|
||||
# List of absolute paths to libraries that should be bundled with the plugin.
|
||||
# This list could contain prebuilt libraries, or libraries created by an
|
||||
# external build triggered from this build file.
|
||||
set(tor_bundled_libraries
|
||||
# Defined in ../src/CMakeLists.txt.
|
||||
# This can be changed to accommodate different builds.
|
||||
"${${PROJECT_NAME}_cargokit_lib}"
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Relative import to be able to reuse the C sources.
|
||||
// See the comment in ../{projectName}}.podspec for more information.
|
||||
@@ -1,39 +0,0 @@
|
||||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint tor.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'tor'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'A new Flutter FFI plugin project.'
|
||||
s.description = <<-DESC
|
||||
A new Flutter FFI plugin project.
|
||||
DESC
|
||||
s.homepage = 'http://example.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Your Company' => 'email@example.com' }
|
||||
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.platform = :osx, '10.13'
|
||||
|
||||
s.script_phase = {
|
||||
:name => 'Build Rust library',
|
||||
# First argument is relative path to the `rust` folder, second is name of rust library
|
||||
:script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust tor',
|
||||
:execution_position => :before_compile,
|
||||
:input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'],
|
||||
# Let XCode know that the static library referenced in -force_load below is
|
||||
# created by this build step.
|
||||
:output_files => ["${BUILT_PRODUCTS_DIR}/libtor.a"],
|
||||
}
|
||||
s.pod_target_xcconfig = {
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
# We use `-force_load` instead of `-l` since Xcode strips out unused symbols from static libraries.
|
||||
'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libtor.a',
|
||||
'DEAD_CODE_STRIPPING' => 'YES',
|
||||
'STRIP_INSTALLED_PRODUCT[config=Release][sdk=*][arch=*]' => "YES",
|
||||
'STRIP_STYLE[config=Release][sdk=*][arch=*]' => "non-global",
|
||||
'DEPLOYMENT_POSTPROCESSING[config=Release][sdk=*][arch=*]' => "YES",
|
||||
}
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,92 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: tor
|
||||
description: A multi-platform Flutter plugin for managing a Tor proxy. Based on arti.
|
||||
version: 0.0.9
|
||||
homepage: https://github.com/Foundation-Devices/tor
|
||||
resolution: workspace
|
||||
|
||||
platforms:
|
||||
android:
|
||||
ios:
|
||||
linux:
|
||||
macos:
|
||||
windows:
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.0
|
||||
flutter: '>=3.3.0'
|
||||
|
||||
dependencies:
|
||||
ffi: ^2.1.4
|
||||
flutter:
|
||||
sdk: flutter
|
||||
path_provider: ^2.1.5
|
||||
plugin_platform_interface: ^2.1.8
|
||||
|
||||
dev_dependencies:
|
||||
ffigen: ^20.1.1
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
lint: ^2.8.0
|
||||
|
||||
ffigen:
|
||||
output: 'lib/src/generated_bindings.dart'
|
||||
llvm-path:
|
||||
- '/usr/lib/llvm-14/lib/libclang.so.1'
|
||||
functions:
|
||||
include:
|
||||
- tor_.*
|
||||
structs:
|
||||
exclude:
|
||||
- .*
|
||||
unions:
|
||||
exclude:
|
||||
- .*
|
||||
enums:
|
||||
exclude:
|
||||
- _.*
|
||||
unnamed-enums:
|
||||
exclude:
|
||||
- _.*
|
||||
macros:
|
||||
exclude:
|
||||
- _.*
|
||||
globals:
|
||||
exclude:
|
||||
- _.*
|
||||
headers:
|
||||
entry-points:
|
||||
- 'rust/target/tor.h'
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
|
||||
# which should be registered in the plugin registry. This is required for
|
||||
# using method channels.
|
||||
# The Android 'package' specifies package in which the registered class is.
|
||||
# This is required for using method channels on Android.
|
||||
# The 'ffiPlugin' specifies that native code should be built and bundled.
|
||||
# This is required for using `dart:ffi`.
|
||||
# All these are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
#
|
||||
# Please refer to README.md for a detailed explanation.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
ffiPlugin: true
|
||||
ios:
|
||||
ffiPlugin: true
|
||||
linux:
|
||||
ffiPlugin: true
|
||||
macos:
|
||||
ffiPlugin: true
|
||||
windows:
|
||||
ffiPlugin: true
|
||||
Generated
-5878
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,31 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
[package]
|
||||
name = "tor"
|
||||
version = "0.0.9"
|
||||
authors = ["Igor Cota <igor@foundation.xyz>"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.5"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
arti-client = { version = "0.36.0", features = ["static", "onion-service-client"] }
|
||||
arti = { version = "1.7.0", features = ["experimental-api", "static"] }
|
||||
tor-rtcompat = { version = "0.36.0", features = ["static"] }
|
||||
tor-config = "0.36.0"
|
||||
log = "0.4.28"
|
||||
#android_log-sys = "0.3.1"
|
||||
rlimit = "0.10.2"
|
||||
anyhow = "1.0.100"
|
||||
time = "0.3.44"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["fmt", "tracing-log", "env-filter"] }
|
||||
tracing-android = "0.2.0"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "=0.24.3"
|
||||
@@ -1,36 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Foundation Devices Inc.
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use cbindgen::{Config, Language};
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Create C header files for Dart
|
||||
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let package_name = env::var("CARGO_PKG_NAME").unwrap();
|
||||
|
||||
let output_file = target_dir()
|
||||
.join(format!("{}.h", package_name))
|
||||
.display()
|
||||
.to_string();
|
||||
|
||||
let config = Config {
|
||||
language: Language::C,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
cbindgen::generate_with_config(crate_dir, config)
|
||||
.unwrap()
|
||||
.write_to_file(output_file);
|
||||
}
|
||||
|
||||
fn target_dir() -> PathBuf {
|
||||
if let Ok(target) = env::var("CARGO_TARGET_DIR") {
|
||||
PathBuf::from(target)
|
||||
} else {
|
||||
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("target")
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
[toolchain]
|
||||
channel = "1.87.0"
|
||||
@@ -1,3 +0,0 @@
|
||||
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
@@ -1,61 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Foundation Devices Inc.
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use log::{error, warn};
|
||||
use std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
use std::ffi::{c_char, CString};
|
||||
//pub(crate) use crate::unwrap_or_return;
|
||||
|
||||
thread_local! {
|
||||
static LAST_ERROR: RefCell<Option<Box<dyn Error>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tor_last_error_message() -> *const c_char {
|
||||
let last_error = match crate::error::take_last_error() {
|
||||
Some(err) => err,
|
||||
None => return CString::new("").unwrap().into_raw(),
|
||||
};
|
||||
|
||||
let error_message = last_error.to_string();
|
||||
CString::new(error_message).unwrap().into_raw()
|
||||
}
|
||||
|
||||
macro_rules! unwrap_or_return {
|
||||
($a:expr,$b:expr) => {
|
||||
match $a {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
update_last_error(e);
|
||||
return $b;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Update the most recent error, clearing whatever may have been there before.
|
||||
pub fn update_last_error<E: Error + 'static>(err: E) {
|
||||
error!("Setting LAST_ERROR: {}", err);
|
||||
|
||||
{
|
||||
// Print a pseudo-backtrace for this error, following back each error's
|
||||
// cause until we reach the root error.
|
||||
let mut cause = err.source();
|
||||
while let Some(parent_err) = cause {
|
||||
warn!("Caused by: {}", parent_err);
|
||||
cause = parent_err.source();
|
||||
}
|
||||
}
|
||||
|
||||
LAST_ERROR.with(|prev| {
|
||||
*prev.borrow_mut() = Some(Box::new(err));
|
||||
});
|
||||
}
|
||||
|
||||
/// Retrieve the most recent error, clearing it in the process.
|
||||
pub fn take_last_error() -> Option<Box<dyn Error>> {
|
||||
LAST_ERROR.with(|prev| prev.borrow_mut().take())
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Foundation Devices Inc.
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::error::update_last_error;
|
||||
use arti_client::config::pt::TransportConfigBuilder;
|
||||
use arti_client::config::CfgPath;
|
||||
use arti_client::{DormantMode, TorClient, TorClientConfig};
|
||||
use lazy_static::lazy_static;
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::net::SocketAddr;
|
||||
use std::{io, ptr};
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
use tokio::task::JoinHandle;
|
||||
use tor_config::Listen;
|
||||
use tor_rtcompat::tokio::TokioNativeTlsRuntime;
|
||||
use tor_rtcompat::ToplevelBlockOn;
|
||||
#[allow(unused_imports)]
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing_subscriber::fmt::Subscriber;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
// static LOG_FILTER: &str = "info,arti_client=debug,tor_chanmgr=debug,tor_proto=debug";
|
||||
|
||||
static LOG_FILTER: &str = "info";
|
||||
|
||||
pub use crate::error::tor_last_error_message;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub use crate::util::tor_get_nofile_limit;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub use crate::util::tor_set_nofile_limit;
|
||||
|
||||
#[macro_use]
|
||||
mod error;
|
||||
mod util;
|
||||
|
||||
lazy_static! {
|
||||
static ref RUNTIME: io::Result<Runtime> = Builder::new_multi_thread().enable_all().build();
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Tor {
|
||||
client: *mut c_void,
|
||||
proxy: *mut c_void,
|
||||
}
|
||||
|
||||
struct TorConfig {
|
||||
state_dir: String,
|
||||
cache_dir: String,
|
||||
obfs4_port: u16,
|
||||
snowflake_port: u16,
|
||||
bridge_lines: Option<String>,
|
||||
}
|
||||
|
||||
fn build_tor_client_config(
|
||||
config: &TorConfig,
|
||||
) -> Result<TorClientConfig, tor_config::ConfigBuildError> {
|
||||
let mut cfg_builder = TorClientConfig::builder();
|
||||
|
||||
cfg_builder
|
||||
.storage()
|
||||
.state_dir(CfgPath::new(config.state_dir.clone()))
|
||||
.cache_dir(CfgPath::new(config.cache_dir.clone()));
|
||||
|
||||
cfg_builder.address_filter().allow_onion_addrs(false);
|
||||
|
||||
if config.obfs4_port > 0 {
|
||||
let mut transport = TransportConfigBuilder::default();
|
||||
transport
|
||||
.protocols(vec!["obfs4".parse().unwrap()])
|
||||
.proxy_addr(SocketAddr::new(
|
||||
"127.0.0.1".parse().unwrap(),
|
||||
config.obfs4_port,
|
||||
));
|
||||
cfg_builder.bridges().transports().push(transport);
|
||||
}
|
||||
|
||||
if config.snowflake_port > 0 {
|
||||
let mut transport = TransportConfigBuilder::default();
|
||||
transport
|
||||
.protocols(vec!["snowflake".parse().unwrap()])
|
||||
.proxy_addr(SocketAddr::new(
|
||||
"127.0.0.1".parse().unwrap(),
|
||||
config.snowflake_port,
|
||||
));
|
||||
cfg_builder.bridges().transports().push(transport);
|
||||
}
|
||||
|
||||
if let Some(ref bridge_lines) = config.bridge_lines {
|
||||
for bridge_line in bridge_lines.split('\n') {
|
||||
match bridge_line.parse() {
|
||||
Ok(bridge) => {
|
||||
cfg_builder.bridges().bridges().push(bridge);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to parse bridge line '{}': {}", bridge_line, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_builder.build()
|
||||
}
|
||||
|
||||
unsafe fn parse_config_from_c_params(
|
||||
state_dir: *const c_char,
|
||||
cache_dir: *const c_char,
|
||||
obfs4_port: u16,
|
||||
snowflake_port: u16,
|
||||
bridge_lines: *const c_char,
|
||||
) -> Result<TorConfig, std::str::Utf8Error> {
|
||||
let state_dir = CStr::from_ptr(state_dir).to_str()?.to_owned();
|
||||
let cache_dir = CStr::from_ptr(cache_dir).to_str()?.to_owned();
|
||||
|
||||
let bridge_lines = if bridge_lines.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(CStr::from_ptr(bridge_lines).to_str()?.to_owned())
|
||||
};
|
||||
|
||||
Ok(TorConfig {
|
||||
state_dir,
|
||||
cache_dir,
|
||||
obfs4_port,
|
||||
snowflake_port,
|
||||
bridge_lines,
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tor_start(
|
||||
socks_port: u16,
|
||||
state_dir: *const c_char,
|
||||
cache_dir: *const c_char,
|
||||
obfs4_port: u16,
|
||||
snowflake_port: u16,
|
||||
bridge_lines: *const c_char,
|
||||
) -> Tor {
|
||||
Subscriber::builder()
|
||||
.with_env_filter(LOG_FILTER)
|
||||
.finish()
|
||||
.with(tracing_android::layer("weblibre_tor").unwrap())
|
||||
.init();
|
||||
|
||||
info!("tor_start");
|
||||
|
||||
let err_ret = Tor {
|
||||
client: ptr::null_mut(),
|
||||
proxy: ptr::null_mut(),
|
||||
};
|
||||
|
||||
let config = unwrap_or_return!(
|
||||
parse_config_from_c_params(
|
||||
state_dir,
|
||||
cache_dir,
|
||||
obfs4_port,
|
||||
snowflake_port,
|
||||
bridge_lines
|
||||
),
|
||||
err_ret
|
||||
);
|
||||
|
||||
let runtime = unwrap_or_return!(TokioNativeTlsRuntime::create(), err_ret);
|
||||
let cfg = unwrap_or_return!(build_tor_client_config(&config), err_ret);
|
||||
|
||||
let client = unwrap_or_return!(
|
||||
runtime.block_on(async {
|
||||
TorClient::with_runtime(runtime.clone())
|
||||
.config(cfg)
|
||||
.create_bootstrapped()
|
||||
.await
|
||||
}),
|
||||
err_ret
|
||||
);
|
||||
|
||||
let proxy_handle_box = Box::new(start_proxy(socks_port, client.clone()));
|
||||
let client_box = Box::new(client.clone());
|
||||
|
||||
Tor {
|
||||
client: Box::into_raw(client_box) as *mut c_void,
|
||||
proxy: Box::into_raw(proxy_handle_box) as *mut c_void,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tor_reconfigure(
|
||||
client: *mut c_void,
|
||||
state_dir: *const c_char,
|
||||
cache_dir: *const c_char,
|
||||
obfs4_port: u16,
|
||||
snowflake_port: u16,
|
||||
bridge_lines: *const c_char,
|
||||
) -> bool {
|
||||
let config = unwrap_or_return!(
|
||||
parse_config_from_c_params(
|
||||
state_dir,
|
||||
cache_dir,
|
||||
obfs4_port,
|
||||
snowflake_port,
|
||||
bridge_lines
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
let cfg = unwrap_or_return!(build_tor_client_config(&config), false);
|
||||
|
||||
let client = {
|
||||
assert!(!client.is_null());
|
||||
Box::from_raw(client as *mut TorClient<TokioNativeTlsRuntime>)
|
||||
};
|
||||
|
||||
unwrap_or_return!(
|
||||
TorClient::reconfigure(&client, &cfg, tor_config::Reconfigure::WarnOnFailures),
|
||||
false
|
||||
);
|
||||
|
||||
Box::leak(client);
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tor_client_bootstrap(client: *mut c_void) -> bool {
|
||||
let client = {
|
||||
assert!(!client.is_null());
|
||||
Box::from_raw(client as *mut TorClient<TokioNativeTlsRuntime>)
|
||||
};
|
||||
|
||||
unwrap_or_return!(client.runtime().block_on(client.bootstrap()), false);
|
||||
true
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tor_client_set_dormant(client: *mut c_void, soft_mode: bool) {
|
||||
let client = {
|
||||
assert!(!client.is_null());
|
||||
Box::from_raw(client as *mut TorClient<TokioNativeTlsRuntime>)
|
||||
};
|
||||
|
||||
let dormant_mode = if soft_mode {
|
||||
DormantMode::Soft
|
||||
} else {
|
||||
DormantMode::Normal
|
||||
};
|
||||
client.set_dormant(dormant_mode);
|
||||
Box::leak(client);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tor_proxy_stop(proxy: *mut c_void) {
|
||||
let proxy = {
|
||||
assert!(!proxy.is_null());
|
||||
Box::from_raw(proxy as *mut JoinHandle<anyhow::Result<()>>)
|
||||
};
|
||||
|
||||
proxy.abort();
|
||||
}
|
||||
|
||||
fn start_proxy(
|
||||
port: u16,
|
||||
client: TorClient<TokioNativeTlsRuntime>,
|
||||
) -> JoinHandle<anyhow::Result<()>> {
|
||||
println!("Starting proxy!");
|
||||
let rt = RUNTIME.as_ref().unwrap();
|
||||
rt.spawn(arti::proxy::run_proxy(
|
||||
client.runtime().clone(),
|
||||
client.clone(),
|
||||
Listen::new_localhost(port),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn tor_hello() {
|
||||
println!("HELLO THERE");
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::error::update_last_error;
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub unsafe extern "C" fn tor_get_nofile_limit() -> u64 {
|
||||
let nofile_limit = unwrap_or_return!(rlimit::getrlimit(rlimit::Resource::NOFILE), 0);
|
||||
nofile_limit.0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub unsafe extern "C" fn tor_set_nofile_limit(limit: u64) -> u64 {
|
||||
unwrap_or_return!(rlimit::increase_nofile_limit(limit), 0)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user