strip old extensions

This commit is contained in:
Fabian Freund
2026-01-07 06:33:23 +01:00
parent 2fc71cad2e
commit 63712edbc7
104 changed files with 0 additions and 13364 deletions
@@ -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>
@@ -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
}
}
@@ -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)
}
}
@@ -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
-38
View File
@@ -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
-42
View File
@@ -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'
-32
View File
@@ -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
-41
View File
@@ -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.
-9
View File
@@ -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.
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
SPDX-License-Identifier: MIT
-9
View File
@@ -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.
-41
View File
@@ -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.
-40
View File
@@ -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
-13
View File
@@ -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
-73
View File
@@ -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"
}
-5
View File
@@ -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
-8
View File
@@ -1,8 +0,0 @@
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
#
# SPDX-License-Identifier: MIT
target
.dart_tool
*.iml
!pubspec.lock
-39
View File
@@ -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.
-8
View File
@@ -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.
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
SPDX-License-Identifier: MIT
-63
View File
@@ -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']);
});
}
-103
View File
@@ -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
-110
View File
@@ -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.
-180
View File
@@ -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
}
}
}
}
-91
View File
@@ -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
-103
View File
@@ -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
-12
View File
@@ -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.
-37
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
SPDX-License-Identifier: MIT
-12
View File
@@ -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
-3
View File
@@ -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;
-322
View File
@@ -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
View File
@@ -1 +0,0 @@
export 'src/tor.dart';
-27
View File
@@ -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.
-39
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
SPDX-License-Identifier: MIT
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
SPDX-License-Identifier: MIT
-92
View File
@@ -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
-5878
View File
File diff suppressed because it is too large Load Diff
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
SPDX-License-Identifier: MIT
-31
View File
@@ -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"
-36
View File
@@ -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")
}
}
-6
View File
@@ -1,6 +0,0 @@
# SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
#
# SPDX-License-Identifier: MIT
[toolchain]
channel = "1.87.0"
-3
View File
@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2024 Foundation Devices Inc.
SPDX-License-Identifier: MIT
-61
View File
@@ -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())
}
-277
View File
@@ -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");
}
-18
View File
@@ -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