-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into wip/debug-publishing
- Loading branch information
Showing
16 changed files
with
1,823 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"dependencies": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"), | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"dependencies": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.