Skip to content

Commit

Permalink
Merge branch 'main' into wip/debug-publishing
Browse files Browse the repository at this point in the history
  • Loading branch information
kylewlacy committed May 31, 2024
2 parents 09f4920 + d5c77bb commit 089a39a
Show file tree
Hide file tree
Showing 16 changed files with 1,823 additions and 0 deletions.
3 changes: 3 additions & 0 deletions projects/rust/brioche.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dependencies": {}
}
187 changes: 187 additions & 0 deletions projects/rust/project.bri
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import * as std from "std";
import * as TOML from "smol_toml";
import * as t from "typer";
import caCertificates from "ca_certificates";

export const project = {
name: "rust",
version: "1.78.0",
};

const ManifestPkgTarget = t.discriminatedUnion("available", [
t.object({
available: t.literal(true),
hash: t.string(),
url: t.string(),
}),
t.object({
available: t.literal(false),
}),
]);

const ManifestPkg = t.object({
target: t.record(t.string(), ManifestPkgTarget),
});

const Manifest = t.object({
"manifest-version": t.literal("2"),
pkg: t.record(t.string(), ManifestPkg),
profiles: t.record(t.string(), t.array(t.string())),
});

async function rust(): Promise<std.Recipe<std.Directory>> {
const manifestToml = await std
.download({
url: "https://static.rust-lang.org/dist/channel-rust-1.78.0.toml",
hash: std.sha256Hash(
"a29520b3a7245100b20f1701f56cb9d69aa177430f1875156f044a28f1a2c195",
),
})
.read();
const manifest = t.parse(Manifest, TOML.parse(manifestToml));

// TODO: Support other profiles
const profilePackages = manifest.profiles.minimal;
if (profilePackages === undefined) {
throw new Error("Rustup minimal profile not found");
}

let result = std.directory();
for (const pkgName of profilePackages) {
const pkg = manifest.pkg[pkgName];
if (pkg === undefined) {
throw new Error(`Rustup package ${pkgName} not found`);
}

const pkgTarget = pkg.target["x86_64-unknown-linux-gnu"];
if (pkgTarget?.available !== true) {
continue;
}

// FIXME: We unarchive within the process because unarchiving `rust-docs`
// fails for some reason
const pkgTargetArchive = std.download({
url: pkgTarget.url,
hash: std.sha256Hash(pkgTarget.hash),
});

const installedPkg = std.runBash`
tar -xf $pkgTargetArchive --strip-components=1
./install.sh \\
--prefix="$BRIOCHE_OUTPUT" \\
--disable-ldconfig
`
.env({ pkgTargetArchive })
.cast("directory");

result = std.merge(result, installedPkg);
}

const localLibs = await std.runBash`
find lib -type f -name '*.so' -print0 > "$BRIOCHE_OUTPUT"
`
.workDir(result)
.cast("file")
.read()
.then((libs) => libs.split("\0").filter((lib) => lib !== ""));
const localLibNames = localLibs
.map((lib) => lib.split("/").at(-1))
.flatMap((name) => (name != null ? [name] : []));

result = std.autowrap(result, {
executables: [
"bin/cargo",
"bin/rustc",
"bin/rustdoc",
"libexec/rust-analyzer-proc-macro-srv",
],
libraries: [std.tpl`${std.outputPath}/lib`],
skipLibraries: localLibNames,
runtimeLibraryDirs: ["../lib"],
});
result = std.autowrap(result, {
executables: ["lib/librustc_driver-d6f66a8619a171d6.so"],
libraries: [std.tpl`${std.outputPath}/lib`],
});
return result;
}
export default rust;

export interface CargoInstallOptions {
crate: std.AsyncRecipe<std.Directory>;
toolchain?: std.AsyncRecipe<std.Directory>;
profile?: string;
}

export function cargoBuild(options: CargoInstallOptions) {
const toolchain = options.toolchain ?? std.toolchain();

// Create a skeleton crate so we have enough information to vendor the
// dependencies
const skeletonCrate = createSkeletonCrate(options.crate);

// Vendor the dependencies with network access and save the Cargo config.toml
// file, so the vendored dependencies are used
const vendoredSkeletonCrate = std.runBash`
cd "$BRIOCHE_OUTPUT"
mkdir -p .cargo
cargo vendor --locked >> .cargo/config.toml
`
.dependencies(rust(), caCertificates())
.outputScaffold(skeletonCrate)
.unsafe({ networking: true })
.cast("directory");

// Combine the original crate with the vendored dependencies
const crate = std.merge(vendoredSkeletonCrate, options.crate);

// Use `cargo install` to build and install the project to `$BRIOCHE_OUTPUT`
return std.runBash`
cargo install --path . --frozen
`
.dependencies(rust(), toolchain)
.env({
CARGO_INSTALL_ROOT: std.outputPath,
PATH: std.tpl`${std.outputPath}/bin`,
})
.workDir(crate)
.cast("directory");
}

/**
* Create a "skeleton crate" for a Rust crate. This is a crate that has
* the minimal set of files needed for Cargo to consider it a valid crate,
* namely so we can vendor dependencies. Without doing this, we would need
* to re-vendor the crates any time the source code changes!
*/
export function createSkeletonCrate(
crate: std.AsyncRecipe<std.Directory>,
): std.Recipe<std.Directory> {
const recipe = std.runBash`
cargo chef prepare --recipe-path "$BRIOCHE_OUTPUT"
`
.dependencies(rust(), cargoChef())
.workDir(crate)
.cast("file");
return std.runBash`
cd "$BRIOCHE_OUTPUT"
cargo chef cook --recipe-path "$recipe" --no-build
`
.dependencies(rust(), cargoChef())
.env({ recipe })
.outputScaffold(std.directory())
.cast("directory");
}

function cargoChef(): std.Recipe<std.Directory> {
const pkg = std.download({
url: "https://github.com/LukeMathWalker/cargo-chef/releases/download/v0.1.67/cargo-chef-x86_64-unknown-linux-musl.tar.gz",
hash: std.sha256Hash(
"91b518df5c8b02775026875f3aadef1946464354db1ca0758e4912249578f0bc",
),
});

return std.directory({
bin: pkg.unarchive("tar", "gzip"),
});
}
3 changes: 3 additions & 0 deletions projects/smol_toml/brioche.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dependencies": {}
}
152 changes: 152 additions & 0 deletions projects/smol_toml/date.bri
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*!
* Copyright (c) Squirrel Chat et al., All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// Hopefully this class will disappear once Temporal lands in JS
// But for now we have to reinvent the wheel once more!

type Offset = string | null;

const DATE_TIME_RE =
/^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}:\d{2}(?:\.\d+)?)?(Z|[-+]\d{2}:\d{2})?$/i;

export class TomlDate extends Date {
#hasDate = false;
#hasTime = false;
#offset: Offset = null;

constructor(date: string | Date) {
let hasDate = true;
let hasTime = true;
let offset: Offset = "Z";

if (typeof date === "string") {
const match = date.match(DATE_TIME_RE);
if (match != null) {
if (match[1] == null || match[1] === "") {
hasDate = false;
date = `0000-01-01T${date}`;
}

hasTime = match[2] != null && match[2] !== "";
// Do not allow rollover hours
if (match[2] != null && match[2] !== "" && +match[2] > 23) {
date = "";
} else {
offset = match[3] != null && match[3] !== "" ? match[3] : null;
date = date.toUpperCase();
if (offset == null && hasTime) date += "Z";
}
} else {
date = "";
}
}

super(date);
if (!isNaN(this.getTime())) {
this.#hasDate = hasDate;
this.#hasTime = hasTime;
this.#offset = offset;
}
}

isDateTime() {
return this.#hasDate && this.#hasTime;
}

isLocal() {
return (
!this.#hasDate ||
!this.#hasTime ||
this.#offset == null ||
this.#offset === ""
);
}

isDate() {
return this.#hasDate && !this.#hasTime;
}

isTime() {
return this.#hasTime && !this.#hasDate;
}

isValid() {
return this.#hasDate || this.#hasTime;
}

override toISOString() {
const iso = super.toISOString();

// Local Date
if (this.isDate()) return iso.slice(0, 10);

// Local Time
if (this.isTime()) return iso.slice(11, 23);

// Local DateTime
if (this.#offset === null) return iso.slice(0, -1);

// Offset DateTime
if (this.#offset === "Z") return iso;

// This part is quite annoying: JS strips the original timezone from the ISO string representation
// Instead of using a "modified" date and "Z", we restore the representation "as authored"

let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
offset = this.#offset.startsWith("-") ? offset : -offset;

const offsetDate = new Date(this.getTime() - offset * 60e3);
return offsetDate.toISOString().slice(0, -1) + this.#offset;
}

static wrapAsOffsetDateTime(jsDate: Date, offset = "Z") {
const date = new TomlDate(jsDate);
date.#offset = offset;
return date;
}

static wrapAsLocalDateTime(jsDate: Date) {
const date = new TomlDate(jsDate);
date.#offset = null;
return date;
}

static wrapAsLocalDate(jsDate: Date) {
const date = new TomlDate(jsDate);
date.#hasTime = false;
date.#offset = null;
return date;
}

static wrapAsLocalTime(jsDate: Date) {
const date = new TomlDate(jsDate);
date.#hasDate = false;
date.#offset = null;
return date;
}
}
Loading

0 comments on commit 089a39a

Please sign in to comment.