Skip to content

Commit

Permalink
feat(install): automatically migrate package-lock.json to bun.lockb (o…
Browse files Browse the repository at this point in the history
…ven-sh#6352)

* work so far

* stuff

* a

* basics work

* stuff

* yoo

* build lockfile

* correct

* f

* a

* install fixture havent tested

* i made it worse

* lol

* be more reasonable

* make the test easier to pass because bun install doesn't handle obscure lockfile edge cases :/

* a

* works now

* ok

* a

* a

* cool

* nah

* fix stuff

* l

* a

* idfk

* LAME

* prettier errors

* does this fix tests?

* Add more safety checks to Integrity

* Add another check

* More careful lifetime handling

* Fix linux debugger issue

* a

* tmp dir and snapshot test

---------

Co-authored-by: Jarred SUmner <jarred@jarredsumner.com>
  • Loading branch information
paperclover and Jarred-Sumner authored Oct 11, 2023
1 parent 6a17ebe commit 1bf28e0
Show file tree
Hide file tree
Showing 53 changed files with 41,621 additions and 251 deletions.
6 changes: 3 additions & 3 deletions .vscode/launch.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,22 @@ pub fn build(b: *Build) !void {
};
}

const required_zig_version = "0.12.0-dev.163+6780a6bbf";
pub fn build_(b: *Build) !void {
if (!std.mem.eql(u8, @import("builtin").zig_version_string, required_zig_version)) {
const colors = std.io.getStdErr().supportsAnsiEscapeCodes();
std.debug.print(
"{s}WARNING:\nBun requires zig version '{s}', but found '{s}', build may fail...\nMake sure you installed the right version as per https://bun.sh/docs/project/development#install-zig\n{s}You can update to the right version using 'zigup {s}'\n\n",
.{
if (colors) "\x1b[1;33m" else "",
required_zig_version,
@import("builtin").zig_version_string,
if (colors) "\x1b[0m" else "",
required_zig_version,
},
);
}

// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
Expand Down
Binary file modified bun.lockb
Binary file not shown.
20 changes: 19 additions & 1 deletion src/bun.js/bindings/JSCUSocketsLoopIntegration.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
#include "root.h"
#include "JavaScriptCore/VM.h"

// On Linux, signals are used to suspend/resume threads in JavaScriptCore
// When `.acquireAccess` is called, the signal might be raised.
// This causes issues with LLDB which might catch the signal.
// So we want to avoid that, we really only want this code to be executed when the debugger is attached
// But it's pretty hard to tell if LLDB is attached or not, so we just disable this code on Linux when in debug mode
#ifndef ACQUIRE_RELEASE_HEAP_ACCESS
#if OS(DARWIN)
#define ACQUIRE_RELEASE_HEAP_ACCESS 1
#else
#ifndef BUN_DEBUG
#define ACQUIRE_RELEASE_HEAP_ACCESS 1
#endif
#endif
#endif

extern "C" void bun_on_tick_before(JSC::VM* vm)
{
// Let the GC do some work while we are idle
#if ACQUIRE_RELEASE_HEAP_ACCESS
vm->heap.releaseAccess();
#endif
}
extern "C" void bun_on_tick_after(JSC::VM* vm)
{
#if ACQUIRE_RELEASE_HEAP_ACCESS
vm->heap.acquireAccess();
#endif
}
27 changes: 27 additions & 0 deletions src/cli/package_manager_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,32 @@ pub const PackageManagerCommand = struct {
}
}

Global.exit(0);
} else if (strings.eqlComptime(subcommand, "migrate")) {
if (!pm.options.enable.force_save_lockfile) try_load_bun: {
std.fs.cwd().accessZ("bun.lockb", .{ .mode = .read_only }) catch break :try_load_bun;

Output.prettyErrorln(
\\<r><red>error<r>: bun.lockb already exists
\\run with --force to overwrite
, .{});
Global.exit(1);
}
const load_lockfile = @import("../install/migration.zig").detectAndLoadOtherLockfile(
pm.lockfile,
ctx.allocator,
pm.log,
pm.options.lockfile_path,
);
if (load_lockfile == .not_found) {
Output.prettyErrorln(
\\<r><red>error<r>: could not find any other lockfile
, .{});
Global.exit(1);
}
handleLoadLockfileErrors(load_lockfile, pm);
const lockfile = load_lockfile.ok;
lockfile.saveToDisk(pm.options.lockfile_path);
Global.exit(0);
}

Expand All @@ -258,6 +284,7 @@ pub const PackageManagerCommand = struct {
\\ bun pm <b>hash-print<r> print the hash stored in the current lockfile
\\ bun pm <b>cache<r> print the path to the cache folder
\\ bun pm <b>cache rm<r> clear the cache
\\ bun pm <b>migrate<r> migrate another package manager's lockfile without installing anything
\\
\\Learn more about these at <magenta>https://bun.sh/docs/install/utilities<r>
\\
Expand Down
158 changes: 106 additions & 52 deletions src/install/dependency.zig
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ version: Dependency.Version = .{},
/// - `peerDependencies`
/// Technically, having the same package name specified under multiple fields is invalid
/// But we don't want to allocate extra arrays for them. So we use a bitfield instead.
behavior: Behavior = .uninitialized,
behavior: Behavior = Behavior.uninitialized,

/// Sorting order for dependencies is:
/// 1. [ `peerDependencies`, `optionalDependencies`, `devDependencies`, `dependencies` ]
Expand Down Expand Up @@ -147,7 +147,7 @@ pub fn toDependency(
return Dependency{
.name = name,
.name_hash = @as(u64, @bitCast(this[8..16].*)),
.behavior = @as(Dependency.Behavior, @enumFromInt(this[16])),
.behavior = @bitCast(this[16]),
.version = Dependency.Version.toVersion(name, this[17..this.len].*, ctx),
};
}
Expand All @@ -156,7 +156,7 @@ pub fn toExternal(this: Dependency) External {
var bytes: External = undefined;
bytes[0..this.name.bytes.len].* = this.name.bytes;
bytes[8..16].* = @as([8]u8, @bitCast(this.name_hash));
bytes[16] = @intFromEnum(this.behavior);
bytes[16] = @bitCast(this.behavior);
bytes[17..bytes.len].* = this.version.toExternal();
return bytes;
}
Expand Down Expand Up @@ -221,12 +221,16 @@ pub inline fn isGitHubRepoPath(dependency: string) bool {
return hash_index != dependency.len - 1 and first_slash_index > 0 and first_slash_index != dependency.len - 1;
}

// Github allows for the following format of URL:
// https://github.com/<org>/<repo>/tarball/<ref>
// This is a legacy (but still supported) method of retrieving a tarball of an
// entire source tree at some git reference. (ref = branch, tag, etc. Note: branch
// can have arbitrary number of slashes)
/// Github allows for the following format of URL:
/// https://github.com/<org>/<repo>/tarball/<ref>
/// This is a legacy (but still supported) method of retrieving a tarball of an
/// entire source tree at some git reference. (ref = branch, tag, etc. Note: branch
/// can have arbitrary number of slashes)
///
/// This also checks for a github url that ends with ".tar.gz"
pub inline fn isGitHubTarballPath(dependency: string) bool {
if (isTarball(dependency)) return true;

var parts = strings.split(dependency, "/");

var n_parts: usize = 0;
Expand All @@ -248,7 +252,7 @@ pub inline fn isTarball(dependency: string) bool {
}

pub const Version = struct {
tag: Dependency.Version.Tag = .uninitialized,
tag: Tag = .uninitialized,
literal: String = .{},
value: Value = .{ .uninitialized = {} },

Expand Down Expand Up @@ -610,7 +614,7 @@ pub const Version = struct {
}
};

const NpmInfo = struct {
pub const NpmInfo = struct {
name: String,
version: Semver.Query.Group,

Expand All @@ -619,7 +623,7 @@ pub const Version = struct {
}
};

const TagInfo = struct {
pub const TagInfo = struct {
name: String,
tag: String,

Expand All @@ -628,7 +632,7 @@ pub const Version = struct {
}
};

const TarballInfo = struct {
pub const TarballInfo = struct {
uri: URI,
package_name: String = .{},

Expand Down Expand Up @@ -670,7 +674,8 @@ pub inline fn parse(
sliced: *const SlicedString,
log: ?*logger.Log,
) ?Version {
return parseWithOptionalTag(allocator, alias, dependency, null, sliced, log);
const dep = std.mem.trimLeft(u8, dependency, " \t\n\r");
return parseWithTag(allocator, alias, dep, Version.Tag.infer(dep), sliced, log);
}

pub fn parseWithOptionalTag(
Expand Down Expand Up @@ -888,6 +893,12 @@ pub fn parseWithTag(
.literal = sliced.value(),
.value = .{ .tarball = .{ .uri = .{ .local = sliced.sub(dependency[7..]).value() } } },
};
} else if (strings.hasPrefixComptime(dependency, "file:")) {
return .{
.tag = .tarball,
.literal = sliced.value(),
.value = .{ .tarball = .{ .uri = .{ .local = sliced.sub(dependency[5..]).value() } } },
};
} else if (strings.contains(dependency, "://")) {
if (log_) |log| log.addErrorFmt(null, logger.Loc.Empty, allocator, "invalid or unsupported dependency \"{s}\"", .{dependency}) catch unreachable;
return null;
Expand Down Expand Up @@ -950,78 +961,83 @@ pub fn parseWithTag(
}
}

pub const Behavior = enum(u8) {
uninitialized = 0,
_,
pub const Behavior = packed struct(u8) {
pub const uninitialized: Behavior = .{};

// these padding fields are to have compatibility
// with older versions of lockfile v2
_unused_1: u1 = 0,

normal: bool = false,
optional: bool = false,
dev: bool = false,
peer: bool = false,
workspace: bool = false,

_unused_2: u2 = 0,

pub const normal: u8 = 1 << 1;
pub const optional: u8 = 1 << 2;
pub const dev: u8 = 1 << 3;
pub const peer: u8 = 1 << 4;
pub const workspace: u8 = 1 << 5;
pub const normal = Behavior{ .normal = true };
pub const optional = Behavior{ .optional = true };
pub const dev = Behavior{ .dev = true };
pub const peer = Behavior{ .peer = true };
pub const workspace = Behavior{ .workspace = true };

pub inline fn isNormal(this: Behavior) bool {
return (@intFromEnum(this) & Behavior.normal) != 0;
return this.normal;
}

pub inline fn isOptional(this: Behavior) bool {
return (@intFromEnum(this) & Behavior.optional) != 0 and !this.isPeer();
return this.optional and !this.isPeer();
}

pub inline fn isDev(this: Behavior) bool {
return (@intFromEnum(this) & Behavior.dev) != 0;
return this.dev;
}

pub inline fn isPeer(this: Behavior) bool {
return (@intFromEnum(this) & Behavior.peer) != 0;
return this.peer;
}

pub inline fn isWorkspace(this: Behavior) bool {
return (@intFromEnum(this) & Behavior.workspace) != 0;
return this.workspace;
}

pub inline fn setNormal(this: Behavior, value: bool) Behavior {
if (value) {
return @as(Behavior, @enumFromInt(@intFromEnum(this) | Behavior.normal));
} else {
return @as(Behavior, @enumFromInt(@intFromEnum(this) & ~Behavior.normal));
}
var b = this;
b.normal = value;
return b;
}

pub inline fn setOptional(this: Behavior, value: bool) Behavior {
if (value) {
return @as(Behavior, @enumFromInt(@intFromEnum(this) | Behavior.optional));
} else {
return @as(Behavior, @enumFromInt(@intFromEnum(this) & ~Behavior.optional));
}
var b = this;
b.optional = value;
return b;
}

pub inline fn setDev(this: Behavior, value: bool) Behavior {
if (value) {
return @as(Behavior, @enumFromInt(@intFromEnum(this) | Behavior.dev));
} else {
return @as(Behavior, @enumFromInt(@intFromEnum(this) & ~Behavior.dev));
}
var b = this;
b.dev = value;
return b;
}

pub inline fn setPeer(this: Behavior, value: bool) Behavior {
if (value) {
return @as(Behavior, @enumFromInt(@intFromEnum(this) | Behavior.peer));
} else {
return @as(Behavior, @enumFromInt(@intFromEnum(this) & ~Behavior.peer));
}
var b = this;
b.peer = value;
return b;
}

pub inline fn setWorkspace(this: Behavior, value: bool) Behavior {
if (value) {
return @as(Behavior, @enumFromInt(@intFromEnum(this) | Behavior.workspace));
} else {
return @as(Behavior, @enumFromInt(@intFromEnum(this) & ~Behavior.workspace));
}
var b = this;
b.workspace = value;
return b;
}

pub inline fn eq(lhs: Behavior, rhs: Behavior) bool {
return @as(u8, @bitCast(lhs)) == @as(u8, @bitCast(rhs));
}

pub inline fn cmp(lhs: Behavior, rhs: Behavior) std.math.Order {
if (@intFromEnum(lhs) == @intFromEnum(rhs)) {
if (eq(lhs, rhs)) {
return .eq;
}

Expand Down Expand Up @@ -1074,4 +1090,42 @@ pub const Behavior = enum(u8) {
(features.peer_dependencies and this.isPeer()) or
this.isWorkspace();
}

pub fn format(self: Behavior, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
const fields = std.meta.fields(Behavior);
var num_fields: u8 = 0;
inline for (fields) |f| {
if (f.type == bool and @field(self, f.name)) {
num_fields += 1;
}
}
switch (num_fields) {
0 => try writer.writeAll("Behavior.uninitialized"),
1 => {
inline for (fields) |f| {
if (f.type == bool and @field(self, f.name)) {
try writer.writeAll("Behavior." ++ f.name);
break;
}
}
},
else => {
try writer.writeAll("Behavior{");
inline for (fields) |f| {
if (f.type == bool and @field(self, f.name)) {
try writer.writeAll(" " ++ f.name);
}
}
try writer.writeAll(" }");
},
}
}

comptime {
std.debug.assert(@as(u8, @bitCast(Behavior.normal)) == (1 << 1));
std.debug.assert(@as(u8, @bitCast(Behavior.optional)) == (1 << 2));
std.debug.assert(@as(u8, @bitCast(Behavior.dev)) == (1 << 3));
std.debug.assert(@as(u8, @bitCast(Behavior.peer)) == (1 << 4));
std.debug.assert(@as(u8, @bitCast(Behavior.workspace)) == (1 << 5));
}
};
Loading

0 comments on commit 1bf28e0

Please sign in to comment.