aboutsummaryrefslogtreecommitdiff
path: root/src/install/install.zig
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/install/install.zig273
1 files changed, 233 insertions, 40 deletions
diff --git a/src/install/install.zig b/src/install/install.zig
index 7195e1ac5..e7cc1e332 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -485,7 +485,7 @@ const Task = struct {
hasher.update(package_name);
hasher.update("@");
hasher.update(std.mem.asBytes(&package_version));
- return @as(u64, @truncate(u63, hasher.final())) | @as(u64, 1 << 63);
+ return @as(u64, @truncate(u62, hasher.final())) | @as(u64, 1 << 63);
}
pub fn forBinLink(package_id: PackageID) u64 {
@@ -497,7 +497,13 @@ const Task = struct {
_: Task.Tag,
name: string,
) u64 {
- return @as(u64, @truncate(u63, std.hash.Wyhash.hash(0, name)));
+ return @as(u64, @truncate(u62, std.hash.Wyhash.hash(0, name)));
+ }
+
+ pub fn forTarball(url: string) u64 {
+ var hasher = std.hash.Wyhash.init(0);
+ hasher.update(url);
+ return @as(u64, @truncate(u62, hasher.final())) | @as(u64, 1 << 62);
}
};
@@ -564,7 +570,7 @@ const Task = struct {
this.err = err;
this.status = Status.fail;
- this.data = .{ .extract = "" };
+ this.data = .{ .extract = .{} };
this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable;
return;
};
@@ -592,7 +598,7 @@ const Task = struct {
pub const Data = union {
package_manifest: Npm.PackageManifest,
- extract: string,
+ extract: ExtractData,
binlink: bool,
};
@@ -612,6 +618,16 @@ const Task = struct {
};
};
+pub const ExtractData = struct {
+ url: string = "",
+ resolved: string = "",
+ final_path: string = "",
+ json_path: string = "",
+ json_buf: []u8 = "",
+ json_len: usize = 0,
+ dependency_id: PackageID = invalid_package_id,
+};
+
const PackageInstall = struct {
cache_dir: std.fs.IterableDir,
destination_dir: std.fs.IterableDir,
@@ -1789,6 +1805,33 @@ pub const PackageManager = struct {
return this.allocator.create(NetworkTask) catch @panic("Memory allocation failure creating NetworkTask!");
}
+ fn allocGitHubURL(this: *const PackageManager, repository: Repository) !string {
+ var github_api_domain: string = "api.github.com";
+ if (this.env_loader.map.get("GITHUB_API_DOMAIN")) |api_domain| {
+ if (api_domain.len > 0) {
+ github_api_domain = api_domain;
+ }
+ }
+ return try std.fmt.allocPrint(
+ this.allocator,
+ "https://{s}/repos/{s}/{s}/tarball/{s}",
+ .{
+ github_api_domain,
+ this.lockfile.str(&repository.owner),
+ this.lockfile.str(&repository.repo),
+ this.lockfile.str(&repository.committish),
+ },
+ );
+ }
+
+ pub fn cachedGitHubFolderNamePrint(buf: []u8, resolved: string) stringZ {
+ return std.fmt.bufPrintZ(buf, "@GH@{s}", .{resolved}) catch unreachable;
+ }
+
+ pub fn cachedGitHubFolderName(this: *const PackageManager, repository: Repository) stringZ {
+ return cachedGitHubFolderNamePrint(&cached_package_folder_name_buf, this.lockfile.str(&repository.resolved));
+ }
+
// TODO: normalize to alphanumeric
pub fn cachedNPMPackageFolderNamePrint(this: *const PackageManager, buf: []u8, name: string, version: Semver.Version) stringZ {
const scope = this.scopeForPackageName(name);
@@ -1824,34 +1867,52 @@ pub const PackageManager = struct {
// TODO: normalize to alphanumeric
pub fn cachedNPMPackageFolderPrintBasename(buf: []u8, name: string, version: Semver.Version) stringZ {
- const pre_hex_int = version.tag.pre.hash;
- const build_hex_int = version.tag.build.hash;
-
- if (!version.tag.hasPre() and !version.tag.hasBuild()) {
- return std.fmt.bufPrintZ(buf, "{s}@{d}.{d}.{d}", .{ name, version.major, version.minor, version.patch }) catch unreachable;
- } else if (version.tag.hasPre() and version.tag.hasBuild()) {
- return std.fmt.bufPrintZ(
- buf,
- "{s}@{d}.{d}.{d}-{any}+{any}",
- .{ name, version.major, version.minor, version.patch, bun.fmt.hexIntLower(pre_hex_int), bun.fmt.hexIntUpper(build_hex_int) },
- ) catch unreachable;
- } else if (version.tag.hasPre()) {
+ if (version.tag.hasPre()) {
+ if (version.tag.hasBuild()) {
+ return std.fmt.bufPrintZ(
+ buf,
+ "{s}@{d}.{d}.{d}-{any}+{any}",
+ .{
+ name,
+ version.major,
+ version.minor,
+ version.patch,
+ bun.fmt.hexIntLower(version.tag.pre.hash),
+ bun.fmt.hexIntUpper(version.tag.build.hash),
+ },
+ ) catch unreachable;
+ }
return std.fmt.bufPrintZ(
buf,
"{s}@{d}.{d}.{d}-{any}",
- .{ name, version.major, version.minor, version.patch, bun.fmt.hexIntLower(pre_hex_int) },
+ .{
+ name,
+ version.major,
+ version.minor,
+ version.patch,
+ bun.fmt.hexIntLower(version.tag.pre.hash),
+ },
) catch unreachable;
- } else if (version.tag.hasBuild()) {
+ }
+ if (version.tag.hasBuild()) {
return std.fmt.bufPrintZ(
buf,
"{s}@{d}.{d}.{d}+{any}",
- .{ name, version.major, version.minor, version.patch, bun.fmt.hexIntUpper(build_hex_int) },
+ .{
+ name,
+ version.major,
+ version.minor,
+ version.patch,
+ bun.fmt.hexIntUpper(version.tag.build.hash),
+ },
) catch unreachable;
- } else {
- unreachable;
}
-
- unreachable;
+ return std.fmt.bufPrintZ(buf, "{s}@{d}.{d}.{d}", .{
+ name,
+ version.major,
+ version.minor,
+ version.patch,
+ }) catch unreachable;
}
pub fn isFolderInCache(this: *PackageManager, folder_path: stringZ) bool {
@@ -2026,7 +2087,7 @@ pub const PackageManager = struct {
manifest: *const Npm.PackageManifest,
find_result: Npm.PackageManifest.FindResult,
comptime successFn: SuccessFn,
- ) !?ResolvedPackageResult {
+ ) !ResolvedPackageResult {
// Was this package already allocated? Let's reuse the existing one.
if (this.lockfile.getPackageID(
@@ -2619,7 +2680,7 @@ pub const PackageManager = struct {
this.enqueueNetworkTask(network_task);
}
- std.debug.assert(task_id != 0);
+ if (comptime Environment.isDebug) std.debug.assert(task_id != 0);
var manifest_entry_parse = try this.task_queue.getOrPutContext(this.allocator, task_id, .{});
if (!manifest_entry_parse.found_existing) {
@@ -2634,6 +2695,32 @@ pub const PackageManager = struct {
}
return;
},
+ .github => {
+ if (dependency.behavior.isPeer()) return;
+ const dep = dependency.version.value.github;
+ const res = Resolution{
+ .tag = .github,
+ .value = .{
+ .github = dep,
+ },
+ };
+ if (this.lockfile.getPackageID(name_hash, null, res)) |pkg_id| {
+ successFn(this, id, pkg_id);
+ return;
+ }
+ const package = try this.lockfile.appendPackage(.{
+ .name = name,
+ .name_hash = name_hash,
+ .resolution = res,
+ });
+ const url = try this.allocGitHubURL(dep);
+ const task_id = Task.Id.forTarball(url);
+ if (try this.generateNetworkTaskForTarball(task_id, url, package)) |network_task| {
+ network_task.callback.extract.dependency_id = id;
+ this.setPreinstallState(package.meta.id, this.lockfile, .extracting);
+ this.enqueueNetworkTask(network_task);
+ }
+ },
.symlink, .workspace => {
const _result = this.getOrPutResolvedPackage(
alias,
@@ -2704,7 +2791,6 @@ pub const PackageManager = struct {
) catch unreachable;
}
},
-
else => {},
}
}
@@ -2862,6 +2948,67 @@ pub const PackageManager = struct {
}
}
+ fn enqueueDependenciesFromJSON(
+ manager: *PackageManager,
+ package_id: PackageID,
+ data: ExtractData,
+ comptime log_level: Options.LogLevel,
+ ) void {
+ var package = manager.lockfile.packages.get(package_id);
+ switch (package.resolution.tag) {
+ .github => {
+ defer {
+ manager.allocator.free(data.resolved);
+ manager.allocator.free(data.json_buf);
+ }
+ const package_name = package.name;
+ const package_name_hash = package.name_hash;
+ const package_json_source = logger.Source.initPathString(
+ data.json_path,
+ data.json_buf[0..data.json_len],
+ );
+
+ Lockfile.Package.parse(
+ manager.lockfile,
+ &package,
+ manager.allocator,
+ manager.log,
+ package_json_source,
+ void,
+ {},
+ Features.npm,
+ ) catch |err| {
+ if (comptime log_level != .silent) {
+ const string_buf = manager.lockfile.buffers.string_bytes.items;
+ Output.prettyErrorln("<r><red>error:<r> expected package.json in <b>{any}<r> to be a JSON file: {s}\n", .{
+ package.resolution.fmtURL(&manager.options, package_name.slice(string_buf), string_buf),
+ @errorName(err),
+ });
+ }
+ Global.crash();
+ };
+ // package.json might contain a different name than already appended
+ package.name = package_name;
+ package.name_hash = package_name_hash;
+ // stored resolved ID from committish
+ var builder = manager.lockfile.stringBuilder();
+ builder.count(data.resolved);
+ builder.allocate() catch unreachable;
+ package.resolution.value.github.resolved = builder.append(String, data.resolved);
+ builder.clamp();
+ manager.lockfile.packages.set(package_id, package);
+
+ if (package.dependencies.len > 0) {
+ manager.lockfile.scratch.dependency_list_queue.writeItem(package.dependencies) catch unreachable;
+ }
+ if (data.dependency_id < manager.lockfile.buffers.resolutions.items.len) {
+ assignResolution(manager, data.dependency_id, package_id);
+ }
+ },
+ else => {},
+ }
+ }
+
const CacheDir = struct { path: string, is_node_modules: bool };
pub fn fetchCacheDirectoryPath(
env_loader: *DotEnv.Loader,
@@ -3314,7 +3461,7 @@ pub const PackageManager = struct {
manager.setPreinstallState(package_id, manager.lockfile, .done);
if (comptime @TypeOf(callbacks.onExtract) != void) {
- callbacks.onExtract(extract_ctx, package_id, comptime log_level);
+ callbacks.onExtract(extract_ctx, package_id, task.data.extract, comptime log_level);
}
if (comptime log_level.showProgress()) {
@@ -3341,7 +3488,7 @@ pub const PackageManager = struct {
manager.network_resolve_batch = .{};
if (comptime log_level.showProgress()) {
- if (comptime ExtractCompletionContext == void or (@hasField(@TypeOf(callbacks), "progress_bar") and callbacks.progress_bar == true)) {
+ if (@hasField(@TypeOf(callbacks), "progress_bar") and callbacks.progress_bar == true) {
const completed_items = manager.total_tasks - manager.pending_tasks;
if (completed_items != manager.downloads_node.?.unprotected_completed_items or has_updated_this_run) {
manager.downloads_node.?.setCompletedItems(completed_items);
@@ -5517,16 +5664,17 @@ pub const PackageManager = struct {
pub fn installEnqueuedPackages(
this: *PackageInstaller,
package_id: PackageID,
+ data: ExtractData,
comptime log_level: Options.LogLevel,
) void {
const name = this.lockfile.str(&this.names[package_id]);
const resolution = this.resolutions[package_id];
-
- if (this.manager.task_queue.fetchRemove(Task.Id.forNPMPackage(
- Task.Tag.extract,
- name,
- resolution.value.npm.version,
- ))) |removed| {
+ const task_id = switch (resolution.tag) {
+ .github => Task.Id.forTarball(data.url),
+ .npm => Task.Id.forNPMPackage(Task.Tag.extract, name, resolution.value.npm.version),
+ else => unreachable,
+ };
+ if (this.manager.task_queue.fetchRemove(task_id)) |removed| {
var callbacks = removed.value;
defer callbacks.deinit(this.manager.allocator);
@@ -5572,6 +5720,10 @@ pub const PackageManager = struct {
installer.cache_dir_subpath = this.manager.cachedNPMPackageFolderName(name, resolution.value.npm.version);
installer.cache_dir = this.manager.getCacheDirectory();
},
+ .github => {
+ installer.cache_dir_subpath = this.manager.cachedGitHubFolderName(resolution.value.github);
+ installer.cache_dir = this.manager.getCacheDirectory();
+ },
.folder => {
const folder = resolution.value.folder.slice(buf);
// Handle when a package depends on itself via file:
@@ -5735,6 +5887,15 @@ pub const PackageManager = struct {
.fail => |cause| {
if (cause.isPackageMissingFromCache()) {
switch (resolution.tag) {
+ .github => {
+ this.manager.enqueueTarballForDownload(
+ package_id,
+ resolution.value.github,
+ .{
+ .node_modules_folder = @intCast(u32, this.node_modules_folder.dir.fd),
+ },
+ );
+ },
.npm => {
std.debug.assert(resolution.value.npm.url.len() > 0);
this.manager.enqueuePackageForDownload(
@@ -5826,6 +5987,34 @@ pub const PackageManager = struct {
}
}
+ pub fn enqueueTarballForDownload(
+ this: *PackageManager,
+ package_id: PackageID,
+ repository: Repository,
+ task_context: TaskCallbackContext,
+ ) void {
+ const url = this.allocGitHubURL(repository) catch unreachable;
+ const task_id = Task.Id.forTarball(url);
+ var task_queue = this.task_queue.getOrPut(this.allocator, task_id) catch unreachable;
+ if (!task_queue.found_existing) {
+ task_queue.value_ptr.* = .{};
+ }
+
+ task_queue.value_ptr.append(
+ this.allocator,
+ task_context,
+ ) catch unreachable;
+
+ if (!task_queue.found_existing) {
+ if (this.generateNetworkTaskForTarball(task_id, url, this.lockfile.packages.get(package_id)) catch unreachable) |task| {
+ task.schedule(&this.network_tarball_batch);
+ if (this.network_tarball_batch.len > 0) {
+ _ = this.scheduleNetworkTasks();
+ }
+ }
+ }
+ }
+
pub fn installPackages(
this: *PackageManager,
lockfile_: *Lockfile,
@@ -6385,15 +6574,19 @@ pub const PackageManager = struct {
Output.flush();
}
- {
- while (manager.pending_tasks > 0) : (manager.sleep()) {
- try manager.runTasks(void, void{}, .{
- .onExtract = void{},
+ while (manager.pending_tasks > 0) : (manager.sleep()) {
+ try manager.runTasks(
+ *PackageManager,
+ manager,
+ .{
+ .onExtract = PackageManager.enqueueDependenciesFromJSON,
.onResolve = void{},
.onPackageManifestError = void{},
.onPackageDownloadError = void{},
- }, log_level);
- }
+ .progress_bar = true,
+ },
+ log_level,
+ );
}
if (comptime log_level.showProgress()) {