Compare commits

...
15 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 58c8062f93 subprojects: Prepare for release 2025-06-27 15:59:50 +02:00
Kyle Bouchard a6be95d8ae agent: Disable Exceptor for the emulated realm
Exceptor needs to hook signal() and sigaction(), but they are in libc,
leading to gum_mprotect() aborting because it cannot change libc's
'r--' mapping.

I tested it on AVDs with Android 14 and 15 and it prevents the crash
observed in the upstream version when using frida-server or
frida-inject.
2025-06-27 15:58:12 +02:00
Ole André Vadla Ravnås db258f0d35 subprojects: Bump outdated 2025-06-27 15:50:28 +02:00
Francesco Tamagni bea95d0045 buffer: Fix max_length in read_fixed_string
So it is constrained both within the requested size and the buffer's
size.
2025-06-26 12:25:01 +02:00
Ole André Vadla Ravnås 57d2a83548 subprojects: Prepare for release 2025-06-23 18:44:25 +02:00
Ole André Vadla Ravnås 4474490f9b subprojects: Bump outdated 2025-06-23 18:41:09 +02:00
Ole André Vadla Ravnås a9ba854aa7 package-manager: Fix the dev logic 2025-06-22 10:22:42 +02:00
Ole André Vadla Ravnås 47c265b3df package-manager: Only bump if explicitly requested 2025-06-20 21:37:31 +02:00
Ole André Vadla Ravnås 36200d4c17 subprojects: Prepare for release 2025-06-20 15:45:32 +02:00
Ole André Vadla Ravnås bd1f855f4a subprojects: Bump outdated 2025-06-20 15:44:10 +02:00
Ole André Vadla RavnåsandHåvard Sørbø d752b63d3f package-manager: Improve manifest handling
To get us closer to npm's behavior.

Co-authored-by: Håvard Sørbø <[email protected]>
2025-06-20 15:43:29 +02:00
Ole André Vadla Ravnås a66e35d457 package-manager: Improve progress reporting 2025-06-20 11:46:30 +02:00
Ole André Vadla Ravnås 931270de32 package-manager: Refactor a little bit 2025-06-20 11:45:50 +02:00
Ole André Vadla Ravnås 90d54afbf4 promise: Add Future.then() and Future.get_result() 2025-06-20 10:58:00 +02:00
Ole André Vadla Ravnås 0a75b51e7b package-manager: Fix dependency install deadlock
A deadlock could occur when a package's sub-dependency was also a
dependency of another package higher up in the installation stack.
The sub-dependency would wait for the higher-level package to be
physically installed, but that package would not complete its own
installation until its sub-dependencies were resolved, creating a
circular wait.

Refactor the installation logic to first perform the physical
installation of a package. Once the package is on disk, a promise
tracking its physical presence is resolved. Only then does the process
recurse to install the package's own dependencies. This ensures that
any other part of the installation process waiting for this specific
package can proceed, breaking the cycle.
2025-06-20 10:17:38 +02:00
5 changed files with 453 additions and 230 deletions

No files matched your search

+5 -5
View File
@@ -1400,12 +1400,12 @@ namespace Frida.Agent {
if (nb_api.unload_library == null)
parameters.append ("|eternal|sticky");
/*
* Disable ExitMonitor to work around a bug in Android's libndk_translation.so on Android 11.
* We need to avoid modifying libc.so ranges that the translator potentially depends on, to
* avoid blowing up when Interceptor's CPU cache flush results in the translated code being
* discarded, which seems like an edge-case the translator doesn't handle.
* Disable Exceptor and ExitMonitor to work around a bug in Android's libndk_translation.so
* on Android 11. We need to avoid modifying libc.so ranges that the translator potentially
* depends on, to avoid blowing up when Interceptor's CPU cache flush results in the translated
* code being discarded, which seems like an edge-case the translator doesn't handle.
*/
parameters.append ("|exit-monitor:off");
parameters.append ("|exceptor:off|exit-monitor:off");
emulated_bridge_state = new BridgeState (parameters.str);
+1 -1
View File
@@ -446,7 +446,7 @@ namespace Frida {
public string read_fixed_string (size_t offset, size_t size) throws Error {
string * start = (string *) get_pointer (offset, size);
size_t max_length = size - offset;
size_t max_length = size_t.min (size, this.size - offset);
string * end = memchr (start, 0, max_length);
size_t n;
if (end != null)
+52 -12
View File
@@ -46,7 +46,8 @@ namespace Frida {
}
private GLib.Error? _error;
private Gee.ArrayQueue<CompletionFuncEntry> on_complete;
private Gee.ArrayQueue<CompletionFuncEntry>? on_complete;
private Gee.ArrayQueue<ThenHandlerEntry<T>>? on_then;
public async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError {
if (_ready)
@@ -74,7 +75,21 @@ namespace Frida {
return get_result ();
}
private T get_result () throws Frida.Error, IOError {
public void then (owned FutureCompletionHandler<T> handler) {
if (_ready) {
schedule (() => {
handler (this);
return Source.REMOVE;
});
return;
}
if (on_then == null)
on_then = new Gee.ArrayQueue<ThenHandlerEntry<T>> ();
on_then.offer (new ThenHandlerEntry<T> ((owned) handler));
}
public T get_result () throws Frida.Error, IOError {
if (error != null) {
if (error is Frida.Error)
throw (Frida.Error) error;
@@ -111,19 +126,32 @@ namespace Frida {
internal void transition_to_ready () {
_ready = true;
if (on_complete != null && !on_complete.is_empty) {
var source = new IdleSource ();
source.set_priority (Priority.HIGH);
source.set_callback (() => {
CompletionFuncEntry? entry;
while ((entry = on_complete.poll ()) != null)
entry.func ();
on_complete = null;
return false;
if (on_complete != null || on_then != null) {
schedule (() => {
if (on_complete != null) {
CompletionFuncEntry? entry;
while ((entry = on_complete.poll ()) != null)
entry.func ();
on_complete = null;
}
if (on_then != null) {
ThenHandlerEntry? entry;
while ((entry = on_then.poll ()) != null)
entry.handler (this);
on_then = null;
}
return Source.REMOVE;
});
source.attach (MainContext.get_thread_default ());
}
}
private static void schedule (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (MainContext.get_thread_default ());
}
}
private class CompletionFuncEntry {
@@ -133,6 +161,14 @@ namespace Frida {
this.func = (owned) func;
}
}
private class ThenHandlerEntry<T> {
public FutureCompletionHandler<T> handler;
public ThenHandlerEntry (owned FutureCompletionHandler<T> handler) {
this.handler = (owned) handler;
}
}
}
public interface Future<T> : Object {
@@ -140,5 +176,9 @@ namespace Frida {
public abstract T? value { get; }
public abstract GLib.Error? error { get; }
public abstract async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError;
public abstract void then (owned FutureCompletionHandler<T> handler);
public abstract T get_result () throws Frida.Error, IOError;
}
public delegate void FutureCompletionHandler<T> (Future<T> future);
}
+394 -211
View File
@@ -143,6 +143,7 @@ namespace Frida {
});
}
var tracker = new InstallProgressTracker ();
var all_physical_installs = new Gee.HashMap<string, Promise<PackageLockEntry>> ();
var resolution_cache = new Gee.HashMap<string, Promise<ResolvedPackageData>> ();
var packument_cache = new Gee.HashMap<string, Promise<Json.Node>> ();
@@ -164,76 +165,59 @@ namespace Frida {
project_root,
manifest,
prefer_lockfile_for_this_dep,
cancellable,
dep_link_promise,
tracker,
all_physical_installs,
resolution_cache,
packument_cache,
top_level_placements
top_level_placements,
dep_link_promise,
cancellable
);
}
double dep_processing_start_fraction = 0.1;
double dep_processing_span = 0.7;
int completed_top_level_deps = 0;
int num_wanted_deps = wanted_deps_list.size;
if (num_wanted_deps == 0) {
install_progress (RESOLVING_AND_INSTALLING_ALL, dep_processing_start_fraction + dep_processing_span, null);
} else {
foreach (var f in initial_dep_link_futures) {
PackageLockEntry ple = yield f.wait_async (cancellable);
completed_top_level_deps++;
double current_fraction = dep_processing_start_fraction +
((double) completed_top_level_deps / num_wanted_deps) * dep_processing_span;
install_progress (RESOLVING_AND_INSTALLING_ALL, current_fraction, ple.toplevel_dep.name);
}
}
var initial_dep_results = new Gee.ArrayList<PackageLockEntry> ();
foreach (var f in initial_dep_link_futures)
initial_dep_results.add (yield f.wait_async (cancellable));
double physical_completion_start_fraction = dep_processing_start_fraction + dep_processing_span;
double physical_completion_span = 0.05;
install_progress (DEPENDENCIES_PROCESSED, 0.98);
var finished_installs = new Gee.HashMap<string, PackageLockEntry> ();
install_progress (AWAITING_COMPLETION, physical_completion_start_fraction);
var finished_installs = new Gee.TreeMap<string, PackageLockEntry> ();
foreach (var entry in all_physical_installs.entries)
finished_installs[entry.key] = yield entry.value.future.wait_async (cancellable);
install_progress (DEPENDENCIES_PROCESSED, physical_completion_start_fraction + physical_completion_span);
var pkgs = new Gee.ArrayList<Package> ();
var new_manifest_dependencies = new PackageDependencies ();
foreach (var future_ple in initial_dep_link_futures) {
PackageLockEntry ple = yield future_ple.wait_async (cancellable);
foreach (var ple in initial_dep_results) {
if (ple.newly_installed)
pkgs.add (new Package (ple.name, ple.version, ple.description));
var original_dep = ple.toplevel_dep;
var version = specs.has_key (ple.name)
? original_dep.derive_version (ple.version)
: original_dep.version;
new_manifest_dependencies.add (new PackageDependency () {
name = ple.name,
version = original_dep.derive_version (ple.version),
version = version,
role = original_dep.role,
});
}
manifest.dependencies = new_manifest_dependencies;
double finalizing_manifests_start_fraction = physical_completion_start_fraction + physical_completion_span;
install_progress (FINALIZING_MANIFESTS, finalizing_manifests_start_fraction);
yield write_back_manifests (manifest, finished_installs, pkg_json_file, lock_file, cancellable);
install_progress (FINALIZING_MANIFESTS, 0.99);
yield write_back_manifests (manifest, initial_dep_results, finished_installs, pkg_json_file, lock_file,
cancellable);
install_progress (COMPLETE, 1.0);
return new PackageInstallResult (new PackageList (pkgs));
}
private static File install_dir_for_dependency (string name, File node_modules_root) {
File location = node_modules_root;
foreach (unowned string part in name.split ("/"))
location = location.get_child (part);
return location;
}
private class Manifest {
public string? name;
public string? version;
public string? license;
public Gee.List<FundingSource>? funding;
public Gee.Map<string, string>? engines;
public PackageDependencies dependencies = new PackageDependencies ();
public Gee.Map<string, PackageLockPackageInfo> locked_packages = new Gee.HashMap<string, PackageLockPackageInfo> ();
}
@@ -255,11 +239,12 @@ namespace Frida {
private enum PackageRole {
RUNTIME,
DEVELOPMENT,
OPTIONAL
OPTIONAL,
PEER
}
private class PackageDependencies {
public Gee.Map<string, PackageDependency> all = new Gee.HashMap<string, PackageDependency> ();
public Gee.Map<string, PackageDependency> all = new Gee.TreeMap<string, PackageDependency> ();
public Gee.Map<string, PackageDependency> runtime {
get {
@@ -277,15 +262,33 @@ namespace Frida {
}
}
public Gee.Map<string, PackageDependency> optional {
get {
if (_optional == null)
_optional = compute_subset_with_role (OPTIONAL);
return _optional;
}
}
public Gee.Map<string, PackageDependency> peer {
get {
if (_peer == null)
_peer = compute_subset_with_role (PEER);
return _peer;
}
}
private Gee.Map<string, PackageDependency> _runtime;
private Gee.Map<string, PackageDependency> _development;
private Gee.Map<string, PackageDependency> _optional;
private Gee.Map<string, PackageDependency> _peer;
public void add (PackageDependency d) {
all[d.name] = d;
}
private Gee.Map<string, PackageDependency> compute_subset_with_role (PackageRole role) {
var result = new Gee.HashMap<string, PackageDependency> ();
var result = new Gee.TreeMap<string, PackageDependency> ();
foreach (PackageDependency d in all.values) {
if (d.role == role)
result[d.name] = d;
@@ -306,27 +309,40 @@ namespace Frida {
}
}
private class FundingSource {
public string? as_string;
public string? type;
public string? url;
}
private class PackageLockPackageInfo {
public string path_key;
public string? name;
public string? version;
public string? resolved;
public string? integrity;
public string? license;
public Gee.List<FundingSource>? funding;
public Gee.Map<string, string>? engines;
public PackageDependencies dependencies = new PackageDependencies ();
public bool is_dev = false;
public bool is_optional = false;
}
private class PackageLockEntry {
public string id;
public string name;
public string version;
public string resolved;
public string integrity;
public string? description;
public string? license;
public Gee.List<FundingSource>? funding;
public Gee.Map<string, string>? engines;
public PackageDependencies dependencies;
public PackageDependency toplevel_dep;
public bool newly_installed;
public Gee.List<PackageLockEntry> children = new Gee.ArrayList<PackageLockEntry> ();
}
private async Manifest load_manifest (File pkg_json_file, File lock_file, Cancellable? cancellable) throws Error, IOError {
@@ -343,6 +359,9 @@ namespace Frida {
m.version = r.get_string_value ();
r.end_member ();
m.license = read_license (r);
m.funding = read_funding (r);
m.engines = read_engines (r);
m.dependencies = read_dependencies (r);
}
@@ -402,6 +421,9 @@ namespace Frida {
lock_r.end_member ();
}
pli.license = read_license (lock_r);
pli.funding = read_funding (lock_r);
pli.engines = read_engines (lock_r);
pli.dependencies = read_dependencies_from_lock_entry (lock_r, path_key);
lock_r.read_member ("dev");
@@ -423,8 +445,9 @@ namespace Frida {
return m;
}
private async void write_back_manifests (Manifest manifest, Gee.Map<string, PackageLockEntry> installed, File pkg_json_file,
File lock_file, Cancellable? cancellable) throws Error, IOError {
private async void write_back_manifests (Manifest manifest, Gee.List<PackageLockEntry> toplevel_installed,
Gee.Map<string, PackageLockEntry> all_installed, File pkg_json_file, File lock_file,
Cancellable? cancellable) throws Error, IOError {
string? name = null;
Json.Node? root = null;
string? old_pkg_json = null;
@@ -460,13 +483,16 @@ namespace Frida {
}
Json.Object obj = root.get_object ();
var deps_obj = new Json.Object ();
obj.set_object_member ("dependencies", deps_obj);
foreach (PackageLockEntry e in installed.values) {
PackageDependency toplevel_dep = e.toplevel_dep;
if (e.name == toplevel_dep.name)
deps_obj.set_string_member (e.name, toplevel_dep.derive_version (e.version).spec);
}
foreach (PackageDependency dep in manifest.dependencies.runtime.values)
deps_obj.set_string_member (dep.name, dep.version.spec);
var dev_deps_obj = new Json.Object ();
obj.set_object_member ("devDependencies", dev_deps_obj);
foreach (PackageDependency dep in manifest.dependencies.development.values)
dev_deps_obj.set_string_member (dep.name, dep.version.spec);
string new_pkg_json = generate_npm_style_json (root, indent_level, indent_char);
bool pkg_json_changed = old_pkg_json == null || new_pkg_json != old_pkg_json;
@@ -474,9 +500,10 @@ namespace Frida {
yield FS.write_all_text (pkg_json_file, new_pkg_json, cancellable);
var b = new Json.Builder ();
b.begin_object ()
.set_member_name ("name")
.add_string_value (name)
b.begin_object ();
write_name (name, b);
write_version (manifest.version, b);
b
.set_member_name ("lockfileVersion")
.add_int_value (3)
.set_member_name ("requires")
@@ -484,26 +511,19 @@ namespace Frida {
.set_member_name ("packages")
.begin_object ();
b.set_member_name ("")
.begin_object ();
if (manifest.name != null)
b.set_member_name ("name").add_string_value (manifest.name);
if (manifest.version != null)
b.set_member_name ("version").add_string_value (manifest.version);
b.set_member_name ("dependencies").begin_object ();
foreach (PackageLockEntry e in installed.values) {
PackageDependency toplevel_dep = e.toplevel_dep;
if (e.name == toplevel_dep.name) {
b
.set_member_name (e.name)
.add_string_value (toplevel_dep.derive_version (e.version).spec);
}
}
b
.end_object ()
.end_object ();
b.set_member_name ("").begin_object ();
write_name (manifest.name, b);
write_version (manifest.version, b);
write_license (manifest.license, b);
write_funding (manifest.funding, b);
write_dependencies_section ("dependencies", manifest.dependencies.runtime, b);
write_dependencies_section ("devDependencies", manifest.dependencies.development, b);
write_engines (manifest.engines, b);
b.end_object ();
foreach (var entry in installed.entries) {
var runtime_reach = compute_runtime_reach (toplevel_installed, all_installed);
foreach (var entry in all_installed.entries) {
string lockfile_key = entry.key;
PackageLockEntry e = entry.value;
bool is_root_package = lockfile_key == "";
@@ -512,59 +532,26 @@ namespace Frida {
.set_member_name (lockfile_key)
.begin_object ();
if (is_root_package) {
b
.set_member_name ("name")
.add_string_value (e.name);
}
if (is_root_package)
write_name (e.name, b);
write_version (e.version, b);
b
.set_member_name ("version")
.add_string_value (e.version)
.set_member_name ("resolved")
.add_string_value (e.resolved)
.set_member_name ("integrity")
.add_string_value (e.integrity);
if (e.license != null)
b.set_member_name ("license").add_string_value (e.license);
if (!runtime_reach.contains (lockfile_key))
b.set_member_name ("dev").add_boolean_value (true);
if (e.toplevel_dep.role == DEVELOPMENT && e.name == e.toplevel_dep.name) {
b
.set_member_name ("dev")
.add_boolean_value (true);
}
if (!e.dependencies.runtime.is_empty) {
b.set_member_name ("dependencies").begin_object ();
foreach (PackageDependency dep_info in e.dependencies.runtime.values) {
b
.set_member_name (dep_info.name)
.add_string_value (dep_info.version.spec);
}
b.end_object ();
}
if (!e.dependencies.all.is_empty) {
bool has_optional = false;
foreach (PackageDependency dep_info in e.dependencies.all.values) {
if (dep_info.role == OPTIONAL) {
has_optional = true;
break;
}
}
if (has_optional) {
b.set_member_name ("optionalDependencies").begin_object ();
foreach (PackageDependency dep_info in e.dependencies.all.values) {
if (dep_info.role == OPTIONAL) {
b
.set_member_name (dep_info.name)
.add_string_value (dep_info.version.spec);
}
}
b.end_object ();
}
}
write_license (e.license, b);
write_dependencies_section ("dependencies", e.dependencies.runtime, b);
write_engines (e.engines, b);
write_dependencies_section ("optionalDependencies", e.dependencies.optional, b);
write_funding (e.funding, b);
write_dependencies_section ("peerDependencies", e.dependencies.peer, b);
b.end_object ();
}
@@ -577,6 +564,28 @@ namespace Frida {
yield FS.write_all_text (lock_file, lock_json, cancellable);
}
private static Gee.Set<string> compute_runtime_reach (Gee.List<PackageLockEntry> toplevel_installed,
Gee.Map<string, PackageLockEntry> all_installed) {
Gee.Set<string> runtime_reach = new Gee.HashSet<string> ();
Gee.Queue<string> q = new Gee.LinkedList<string> ();
foreach (var ple in toplevel_installed)
if (ple.toplevel_dep.role != DEVELOPMENT)
q.offer (ple.id);
while (!q.is_empty) {
string k = q.poll ();
if (!runtime_reach.add (k))
continue;
PackageLockEntry ple = all_installed[k];
foreach (var sub in ple.children)
q.offer (sub.id);
}
return runtime_reach;
}
private async void perform_install (
string name,
PackageVersion version_spec,
@@ -585,12 +594,13 @@ namespace Frida {
File project_root,
Manifest manifest,
bool prefer_lockfile_for_this_dep,
Cancellable? cancellable,
Promise<PackageLockEntry> dep_link_promise,
InstallProgressTracker tracker,
Gee.Map<string, Promise<PackageLockEntry>> all_physical_installs,
Gee.Map<string, Promise<ResolvedPackageData>> resolution_cache,
Gee.Map<string, Promise<Json.Node>> packument_cache,
Gee.Map<string, string> top_level_placements) {
Gee.Map<string, string> top_level_placements,
Promise<PackageLockEntry> dep_link_promise,
Cancellable? cancellable) {
try {
install_progress (RESOLVING_PACKAGE, -1.0, "%s@%s".printf (name, version_spec.spec));
string resolution_id = name + "@" + version_spec.spec;
@@ -615,7 +625,9 @@ namespace Frida {
integrity = li.integrity,
shasum = null,
description = null,
license = null,
license = li.license,
funding = li.funding,
engines = li.engines,
dependencies = li.dependencies
};
rpd_came_from_lockfile = true;
@@ -672,112 +684,146 @@ namespace Frida {
Promise<PackageLockEntry>? physical_install_promise = all_physical_installs[actual_install_lockfile_key];
if (physical_install_promise != null) {
PackageLockEntry existing_ple = yield physical_install_promise.future.wait_async (cancellable);
dep_link_promise.resolve (new PackageLockEntry () {
id = actual_install_lockfile_key,
name = existing_ple.name,
version = existing_ple.version,
resolved = existing_ple.resolved,
integrity = existing_ple.integrity,
description = existing_ple.description,
license = existing_ple.license,
funding = existing_ple.funding,
engines = existing_ple.engines,
dependencies = existing_ple.dependencies,
toplevel_dep = toplevel_dep,
newly_installed = existing_ple.newly_installed
newly_installed = existing_ple.newly_installed,
});
return;
}
all_physical_installs[actual_install_lockfile_key] = dep_link_promise;
var physical_install_completion = new Promise<PackageLockEntry> ();
all_physical_installs[actual_install_lockfile_key] = physical_install_completion;
bool already_correctly_installed = false;
if (target_dir.query_exists (cancellable)) {
File installed_pkg_json_file = target_dir.get_child ("package.json");
if (installed_pkg_json_file.query_exists (cancellable)) {
try {
Json.Reader installed_pkg_reader =
yield load_json (installed_pkg_json_file, cancellable);
if (installed_pkg_reader.read_member ("version")) {
string? installed_version = installed_pkg_reader.get_string_value ();
installed_pkg_reader.end_member ();
if (installed_version == rpd.effective_version) {
already_correctly_installed = true;
install_progress (PACKAGE_ALREADY_INSTALLED, -1.0,
"%s@%s".printf (rpd.name, rpd.effective_version));
}
}
} catch (Error e) { }
}
}
tracker.total_physical++;
physical_install_completion.future.then (future => {
tracker.completed_physical++;
var sub_dep_futures = new Gee.ArrayList<Future<PackageLockEntry>> ();
File sub_deps_parent_node_modules_dir = target_dir.get_child ("node_modules");
PackageDependencies dependencies_to_recurse = rpd.dependencies;
double dep_processing_start_fraction = 0.1;
double dep_processing_span = 0.88;
double current_fraction = dep_processing_start_fraction
+ ((double) tracker.completed_physical / tracker.total_physical) * dep_processing_span;
if (!dependencies_to_recurse.all.is_empty)
FS.mkdirp (sub_deps_parent_node_modules_dir, cancellable);
foreach (PackageDependency d in dependencies_to_recurse.all.values) {
bool install_this_sub_dep = d.role == RUNTIME || d.role == OPTIONAL;
if (!install_this_sub_dep)
continue;
var sub_dep_link_promise = new Promise<PackageLockEntry> ();
sub_dep_futures.add (sub_dep_link_promise.future);
perform_install.begin (
d.name,
d.version,
toplevel_dep,
sub_deps_parent_node_modules_dir,
project_root,
manifest,
true,
cancellable,
sub_dep_link_promise,
all_physical_installs,
resolution_cache,
packument_cache,
top_level_placements
);
}
foreach (var f in sub_dep_futures)
yield f.wait_async (cancellable);
if (!already_correctly_installed) {
yield download_and_unpack (rpd.name, rpd.effective_version, rpd.resolved_url, target_dir,
rpd.integrity, rpd.shasum, cancellable);
install_progress (PACKAGE_INSTALLED, -1.0, "%s@%s".printf (rpd.name, rpd.effective_version));
}
if (rpd.description == null || rpd.license == null) {
Json.Reader reader = yield load_json (target_dir.get_child ("package.json"), cancellable);
update_rpd_description_license_from_reader (reader, rpd);
}
dep_link_promise.resolve (new PackageLockEntry () {
name = rpd.name,
version = rpd.effective_version,
resolved = rpd.resolved_url,
integrity = rpd.integrity,
description = rpd.description,
license = rpd.license,
dependencies = rpd.dependencies,
toplevel_dep = toplevel_dep,
newly_installed = !already_correctly_installed
install_progress (RESOLVING_AND_INSTALLING_ALL, current_fraction);
});
try {
bool already_correctly_installed = false;
if (target_dir.query_exists (cancellable)) {
File installed_pkg_json_file = target_dir.get_child ("package.json");
if (installed_pkg_json_file.query_exists (cancellable)) {
try {
Json.Reader installed_pkg_reader =
yield load_json (installed_pkg_json_file, cancellable);
if (installed_pkg_reader.read_member ("version")) {
string? installed_version =
installed_pkg_reader.get_string_value ();
installed_pkg_reader.end_member ();
if (installed_version == rpd.effective_version) {
already_correctly_installed = true;
install_progress (PACKAGE_ALREADY_INSTALLED, -1.0,
"%s@%s".printf (rpd.name, rpd.effective_version));
}
}
} catch (Error e) {
}
}
}
if (!already_correctly_installed) {
yield download_and_unpack (rpd.name, rpd.effective_version, rpd.resolved_url, target_dir,
rpd.integrity, rpd.shasum, cancellable);
install_progress (PACKAGE_INSTALLED, -1.0,
"%s@%s".printf (rpd.name, rpd.effective_version));
}
if (rpd.description == null) {
Json.Reader reader = yield load_json (target_dir.get_child ("package.json"), cancellable);
rpd.description = read_description (reader);
}
var ple = new PackageLockEntry () {
id = actual_install_lockfile_key,
name = rpd.name,
version = rpd.effective_version,
resolved = rpd.resolved_url,
integrity = rpd.integrity,
description = rpd.description,
license = rpd.license,
funding = rpd.funding,
engines = rpd.engines,
dependencies = rpd.dependencies,
toplevel_dep = toplevel_dep,
newly_installed = !already_correctly_installed,
};
physical_install_completion.resolve (ple);
var sub_dep_futures = new Gee.ArrayList<Future<PackageLockEntry>> ();
File sub_deps_parent_node_modules_dir = target_dir.get_child ("node_modules");
PackageDependencies dependencies_to_recurse = rpd.dependencies;
if (!dependencies_to_recurse.all.is_empty)
FS.mkdirp (sub_deps_parent_node_modules_dir, cancellable);
foreach (PackageDependency d in dependencies_to_recurse.all.values) {
bool install_this_sub_dep = d.role == RUNTIME;
if (!install_this_sub_dep)
continue;
var sub_dep_link_promise = new Promise<PackageLockEntry> ();
sub_dep_futures.add (sub_dep_link_promise.future);
perform_install.begin (
d.name,
d.version,
toplevel_dep,
sub_deps_parent_node_modules_dir,
project_root,
manifest,
true,
tracker,
all_physical_installs,
resolution_cache,
packument_cache,
top_level_placements,
sub_dep_link_promise,
cancellable
);
}
foreach (var f in sub_dep_futures)
ple.children.add (yield f.wait_async (cancellable));
dep_link_promise.resolve (ple);
} catch (GLib.Error e) {
physical_install_completion.reject (e);
throw e;
}
} catch (GLib.Error e) {
dep_link_promise.reject (e);
}
}
private class ResolvedPackageData {
public string name;
public string effective_version;
public string resolved_url;
public string? integrity;
public string? shasum;
public string? description;
public string? license;
public PackageDependencies dependencies;
private class InstallProgressTracker {
public int total_physical = 0;
public int completed_physical = 0;
}
private static File install_dir_for_dependency (string name, File node_modules_root) {
File location = node_modules_root;
foreach (unowned string part in name.split ("/"))
location = location.get_child (part);
return location;
}
private async void fetch_and_resolve_package_data (string name, PackageVersion version_spec,
@@ -867,6 +913,19 @@ namespace Frida {
}
}
private class ResolvedPackageData {
public string name;
public string effective_version;
public string resolved_url;
public string? integrity;
public string? shasum;
public string? description;
public string? license;
public Gee.List<FundingSource>? funding;
public Gee.Map<string, string>? engines;
public PackageDependencies dependencies;
}
private async void fetch_packument_for_cache (string name, Promise<Json.Node> request, Cancellable? cancellable)
throws Error, IOError {
try {
@@ -880,6 +939,8 @@ namespace Frida {
private static void read_package_version_metadata (Json.Reader reader, ResolvedPackageData rpd) throws Error {
rpd.description = read_description (reader);
rpd.license = read_license (reader);
rpd.funding = read_funding (reader);
rpd.engines = read_engines (reader);
reader.read_member ("dist");
@@ -901,15 +962,20 @@ namespace Frida {
reader.end_member ();
rpd.license = read_license (reader);
rpd.funding = read_funding (reader);
rpd.engines = read_engines (reader);
rpd.dependencies = read_dependencies (reader);
}
private static void update_rpd_description_license_from_reader (Json.Reader reader, ResolvedPackageData rpd) {
if (rpd.description == null)
rpd.description = read_description (reader);
private static void write_name (string? name, Json.Builder b) {
if (name != null)
b.set_member_name ("name").add_string_value (name);
}
if (rpd.license == null)
rpd.license = read_license (reader);
private static void write_version (string? version, Json.Builder b) {
if (version != null)
b.set_member_name ("version").add_string_value (version);
}
private static string? read_description (Json.Reader reader) {
@@ -935,10 +1001,118 @@ namespace Frida {
return license;
}
private static void write_license (string? license, Json.Builder b) {
if (license != null)
b.set_member_name ("license").add_string_value (license);
}
private static Gee.List<FundingSource>? read_funding (Json.Reader reader) throws Error {
if (!reader.read_member ("funding")) {
reader.end_member ();
return null;
}
var result = new Gee.ArrayList<FundingSource> ();
if (reader.is_array ()) {
int n = reader.count_elements ();
for (int i = 0; i < n; i++) {
reader.read_element (i);
read_funding_source (reader, result);
reader.end_element ();
}
} else {
read_funding_source (reader, result);
}
reader.end_member ();
return result.is_empty ? null : result;
}
private static void write_funding (Gee.List<FundingSource>? funding, Json.Builder b) {
if (funding == null)
return;
b.set_member_name ("funding");
if (funding.size == 1) {
write_funding_source (funding.get (0), b);
} else {
b.begin_array ();
foreach (var s in funding)
write_funding_source (s, b);
b.end_array ();
}
}
private static void read_funding_source (Json.Reader r, Gee.List<FundingSource> list) throws Error {
if (r.is_object ()) {
var source = new FundingSource ();
r.read_member ("type");
source.type = r.get_string_value ();
r.end_member ();
r.read_member ("url");
source.url = r.get_string_value ();
r.end_member ();
list.add (source);
} else {
var val = r.get_string_value ();
if (val == null)
throw new Error.PROTOCOL ("Invalid 'funding' field");
list.add (new FundingSource () { as_string = val });
}
}
private static void write_funding_source (FundingSource s, Json.Builder b) {
if (s.as_string != null) {
b.add_string_value (s.as_string);
} else {
b.begin_object ();
if (s.type != null)
b.set_member_name ("type").add_string_value (s.type);
if (s.url != null)
b.set_member_name ("url").add_string_value (s.url);
b.end_object ();
}
}
private static Gee.Map<string, string>? read_engines (Json.Reader reader) throws Error {
if (!reader.read_member ("engines")) {
reader.end_member ();
return null;
}
var result = new Gee.TreeMap<string, string> ();
string[]? members = reader.list_members ();
if (members == null)
throw new Error.PROTOCOL ("Invalid 'engines' field");
foreach (unowned string name in members) {
reader.read_member (name);
string? val = reader.get_string_value ();
if (val == null)
throw new Error.PROTOCOL ("Invalid 'engines' value");
result[name] = val;
reader.end_member ();
}
reader.end_member ();
return result;
}
private static void write_engines (Gee.Map<string, string>? engines, Json.Builder b) {
if (engines == null)
return;
b.set_member_name ("engines").begin_object ();
foreach (var e in engines.entries)
b.set_member_name (e.key).add_string_value (e.value);
b.end_object ();
}
private static PackageDependencies read_dependencies (Json.Reader r) throws Error {
var deps = new PackageDependencies ();
string[] section_keys = {"dependencies", "devDependencies"};
string[] section_keys = { "dependencies", "devDependencies", "optionalDependencies", "peerDependencies" };
PackageRole[] section_roles = { RUNTIME, DEVELOPMENT, OPTIONAL, PEER };
for (uint i = 0; i != section_keys.length; i++) {
unowned string section_key = section_keys[i];
@@ -951,7 +1125,7 @@ namespace Frida {
if (names == null)
throw new Error.PROTOCOL ("Invalid package.json section for '%s'", section_key);
PackageRole role = (i == 0) ? PackageRole.RUNTIME : PackageRole.DEVELOPMENT;
PackageRole role = section_roles[i];
foreach (unowned string name in names) {
r.read_member (name);
@@ -979,7 +1153,8 @@ namespace Frida {
private static PackageDependencies read_dependencies_from_lock_entry (Json.Reader r, string parent_path_key) throws Error {
var deps = new PackageDependencies ();
string[] section_keys = { "dependencies", "optionalDependencies" };
string[] section_keys = { "dependencies", "optionalDependencies", "peerDependencies" };
PackageRole[] section_roles = { RUNTIME, OPTIONAL, PEER };
for (uint i = 0; i != section_keys.length; i++) {
unowned string section_key = section_keys[i];
@@ -994,7 +1169,7 @@ namespace Frida {
section_key, parent_path_key);
}
PackageRole role = (i == 1) ? PackageRole.OPTIONAL : PackageRole.RUNTIME;
PackageRole role = section_roles[i];
foreach (unowned string dep_name in dep_names) {
r.read_member (dep_name);
@@ -1020,6 +1195,15 @@ namespace Frida {
return deps;
}
private static void write_dependencies_section (string section, Gee.Map<string, PackageDependency> deps, Json.Builder b) {
if (deps.is_empty)
return;
b.set_member_name (section).begin_object ();
foreach (PackageDependency dep in deps.values)
b.set_member_name (dep.name).add_string_value (dep.version.spec);
b.end_object ();
}
private async void download_and_unpack (string name, string version, string tarball_url, File dest_root, string? integrity,
string? shasum, Cancellable? cancellable) throws Error, IOError {
int io_priority = Priority.DEFAULT;
@@ -1195,7 +1379,6 @@ namespace Frida {
DOWNLOADING_PACKAGE,
PACKAGE_INSTALLED,
RESOLVING_AND_INSTALLING_ALL,
AWAITING_COMPLETION,
DEPENDENCIES_PROCESSED,
FINALIZING_MANIFESTS,
COMPLETE,
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.2.3
revision = 17.2.6
depth = 1
[provide]