diff --git a/.envrc b/.envrc index 96052e2..a5dbbcb 100644 --- a/.envrc +++ b/.envrc @@ -1,7 +1 @@ -use_riff() { - watch_file Cargo.toml Cargo.lock - eval "$(riff print-dev-env)" -} - -use riff use flake . diff --git a/Cargo.lock b/Cargo.lock index f5b406a..8bd2c12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -968,6 +968,7 @@ dependencies = [ "tracing-error", "tracing-subscriber", "uuid", + "walkdir", "xdg", "zeroize", ] @@ -984,6 +985,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.20" @@ -1459,6 +1469,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 2e78243..412135b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ tracing = "0.1.36" tracing-error = "0.2.0" tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } uuid = { version = "1.1.2", features = [ "v4", "fast-rng", "serde" ]} +walkdir = "2.3.2" xdg = "2" zeroize = "1.5.7" diff --git a/registry/format.sh b/registry/format.sh index effe5c7..f16a295 100755 --- a/registry/format.sh +++ b/registry/format.sh @@ -1,5 +1,5 @@ #!/bin/sh - +set -exuo pipefail cd "$(dirname "$0")" jq -S < registry.json > registry.next.json diff --git a/registry/registry.json b/registry/registry.json index 3021e94..3c88b81 100644 --- a/registry/registry.json +++ b/registry/registry.json @@ -1,5 +1,142 @@ { "language": { + "go": { + "default": { + "build-inputs": [ + "go" + ] + }, + "dependencies": { + "???": { + "build-inputs": [ + "xkbcommon", + "wayland", + "pkg-config" + ] + }, + "github.com/containerd/btrfs": { + "build-inputs": [ + "btrfs-progs" + ] + }, + "github.com/containers/image/v5/ostree": { + "build-inputs": [ + "ostree", + "glib", + "libselinux", + "pkg-config" + ] + }, + "github.com/containers/storage/drivers/btrfs": { + "build-inputs": [ + "btrfs-progs" + ] + }, + "github.com/containers/storage/drivers/devmapper": { + "build-inputs": [ + "pkg-config", + "lvm2" + ] + }, + "github.com/containers/storage/pkg/devicemapper": { + "build-inputs": [ + "lvm2" + ] + }, + "github.com/containers/storage/pkg/idtools": { + "build-inputs": [ + "shadow" + ] + }, + "github.com/coreos/go-systemd/sdjournal": { + "build-inputs": [ + "systemd" + ] + }, + "github.com/go-gl/glfw/v3.3/glfw": { + "build-inputs": [ + "pkg-config", + "xorg.libX11", + "xorg.libXcursor", + "xorg.libXrandr", + "xorg.libXinerama", + "xorg.libXi", + "xorg.libXext", + "xorg.libXxf86vm", + "libGL" + ] + }, + "github.com/google/gopacket/pcap": { + "build-inputs": [ + "libpcap" + ] + }, + "github.com/google/seesaw/netlink": { + "build-inputs": [ + "libnl" + ] + }, + "github.com/ostreedev/ostree-go/pkg/glibobject": { + "build-inputs": [ + "glib", + "pkg-config" + ] + }, + "github.com/ostreedev/ostree-go/pkg/otbuiltin": { + "build-inputs": [ + "ostree", + "pkg-config" + ] + }, + "github.com/proglottis/gpgme": { + "build-inputs": [ + "pkg-config", + "gpgme" + ] + }, + "github.com/seccomp/libseccomp-golang": { + "build-inputs": [ + "libseccomp", + "pkg-config" + ] + }, + "github.com/tecbot/gorocksdb": { + "build-inputs": [ + "rocksdb" + ] + } + } + }, + "javascript": { + "default": { + "build-inputs": [ + "nodejs", + "yarn", + "nodePackages.npm" + ] + }, + "dependencies": { + "cypress": { + "build-inputs": [ + "cypress" + ], + "environment-variables": { + "CYPRESS_INSTALL_BINARY": "0", + "CYPRESS_RUN_BINARY": "${cypress}/bin/Cypress" + } + }, + "karma": { + "environment-variables": { + "CHROME_BIN": "${google-chrome}/bin/google-chrome-stable" + } + }, + "node-gyp": { + "build-inputs": [ + "python3" + ] + } + } + }, "rust": { "default": { "build-inputs": [ diff --git a/src/dependency_registry/go.rs b/src/dependency_registry/go.rs new file mode 100644 index 0000000..b1478f8 --- /dev/null +++ b/src/dependency_registry/go.rs @@ -0,0 +1,15 @@ +use std::collections::HashMap; +use super::rust::RustDependencyData; +use serde::Deserialize; + +// Cribbing RustDependencyData here because there's nothing really +// rust-specific about it besides the name. + +// Not just reusing RustDependencyRegistryData entirely, because +// there's at least the conceptual difference that the map keys +// are Go package paths and not plain crate URLs +#[derive(Deserialize, Default, Clone, Debug)] +pub struct GoDependencyRegistryData { + pub(crate) default: RustDependencyData, + pub(crate) dependencies: HashMap, +} diff --git a/src/dependency_registry/javascript.rs b/src/dependency_registry/javascript.rs new file mode 100644 index 0000000..0af5885 --- /dev/null +++ b/src/dependency_registry/javascript.rs @@ -0,0 +1,297 @@ +use std::collections::{HashMap, HashSet}; + +use serde::Deserialize; + +use crate::dev_env::{DevEnvironment, DevEnvironmentAppliable}; + +/// A language specific registry of dependencies to riff settings +#[derive(Deserialize, Default, Clone, Debug)] +pub struct JavascriptDependencyRegistryData { + /// Settings which are needed for every instance of this language (Eg `cargo` for Rust) + pub(crate) default: JavascriptDependencyTargetData, + /// A mapping of dependencies (by crate name) to configuration + // TODO(@hoverbear): How do we handle crates with conflicting names? eg a `rocksdb-sys` crate from one repo and another from another having different requirements? + pub(crate) dependencies: HashMap, +} + +#[derive(Deserialize, Default, Clone, Debug)] +pub struct JavascriptDependencyData { + #[serde(flatten)] + pub(crate) default: JavascriptDependencyTargetData, + // Keep the key a `String` since users can make custom targets. + #[serde(default)] + pub(crate) targets: HashMap, +} + +impl JavascriptDependencyData { + #[tracing::instrument(skip_all)] + pub(crate) fn build_inputs(&self) -> HashSet { + let target = format!("{}", target_lexicon::HOST); + let mut build_inputs = self.default.build_inputs.clone(); + // Importantly: These come after, they are more specific. + if let Some(target_config) = self.targets.get(&target) { + build_inputs = build_inputs + .union(&target_config.build_inputs) + .cloned() + .collect(); + } + build_inputs + } + #[tracing::instrument(skip_all)] + pub(crate) fn environment_variables(&self) -> HashMap { + let target = format!("{}", target_lexicon::HOST); + let mut environment_variables = self.default.environment_variables.clone(); + // Importantly: These come after, they are more specific. + if let Some(target_config) = self.targets.get(&target) { + for (k, v) in &target_config.environment_variables { + environment_variables.insert(k.clone(), v.clone()); + } + } + environment_variables + } + #[tracing::instrument(skip_all)] + pub(crate) fn runtime_inputs(&self) -> HashSet { + let target = format!("{}", target_lexicon::HOST); + let mut runtime_inputs = self.default.runtime_inputs.clone(); + // Importantly: These come after, they are more specific. + if let Some(target_config) = self.targets.get(&target) { + runtime_inputs = runtime_inputs + .union(&target_config.runtime_inputs) + .cloned() + .collect(); + } + runtime_inputs + } +} + +impl DevEnvironmentAppliable for JavascriptDependencyData { + #[tracing::instrument(skip_all)] + fn apply(&self, dev_env: &mut DevEnvironment) { + self.default.apply(dev_env); + let target = format!("{}", target_lexicon::HOST); + // Importantly: These come after, they are more specific. + if let Some(target_config) = self.targets.get(&target) { + target_config.apply(dev_env); + } + } +} + +/// Dependency specific information needed for riff +#[derive(Deserialize, Default, Clone, Debug)] +pub struct JavascriptDependencyTargetData { + /// The Nix `buildInputs` needed + #[serde(default, rename = "build-inputs")] + pub(crate) build_inputs: HashSet, + /// Any packaging specific environment variables that need to be set + #[serde(default, rename = "environment-variables")] + pub(crate) environment_variables: HashMap, + /// The Nix packages which should have the result of `lib.getLib` run on them placed on the `LD_LIBRARY_PATH` + #[serde(default, rename = "runtime-inputs")] + pub(crate) runtime_inputs: HashSet, +} + +impl DevEnvironmentAppliable for JavascriptDependencyTargetData { + #[tracing::instrument(skip_all)] + fn apply(&self, dev_env: &mut DevEnvironment) { + dev_env.build_inputs = dev_env + .build_inputs + .union(&self.build_inputs) + .cloned() + .collect(); + for (ref env_key, ref env_val) in &self.environment_variables { + if let Some(existing_value) = dev_env + .environment_variables + .insert(env_key.to_string(), env_val.to_string()) + { + tracing::debug!( + key = env_key, + existing_value, + new_value = env_val, + "Overriding previously declared environment variable" + ) + } + } + dev_env.runtime_inputs = dev_env + .runtime_inputs + .union(&self.runtime_inputs) + .cloned() + .collect(); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::dependency_registry::DependencyRegistry; + use tempfile::TempDir; + + #[tokio::test] + async fn try_apply() -> eyre::Result<()> { + let cache_dir = TempDir::new()?; + std::env::set_var("XDG_CACHE_HOME", cache_dir.path()); + let registry = DependencyRegistry::new(true).await?; + let mut dev_env = DevEnvironment::new(®istry); + + let target = format!("{}", target_lexicon::HOST); + let data = JavascriptDependencyData { + default: JavascriptDependencyTargetData { + build_inputs: vec!["default".into()].into_iter().collect(), + environment_variables: vec![ + ("DEFAULT_VAR".into(), "default".into()), + ("CONFLICT".into(), "default".into()), + ] + .into_iter() + .collect(), + runtime_inputs: vec!["default".into()].into_iter().collect(), + }, + targets: { + let mut map = HashMap::default(); + map.insert( + target, + JavascriptDependencyTargetData { + build_inputs: vec!["target_specific".into()].into_iter().collect(), + environment_variables: vec![ + ("TARGET_VAR".into(), "target_specific".into()), + ("CONFLICT".into(), "target_specific".into()), + ] + .into_iter() + .collect(), + runtime_inputs: vec!["target_specific".into()].into_iter().collect(), + }, + ); + map + }, + }; + + data.apply(&mut dev_env); + + assert_eq!( + dev_env.build_inputs, + vec!["default".into(), "target_specific".into()] + .into_iter() + .collect() + ); + assert_eq!( + dev_env.environment_variables, + vec![ + ("DEFAULT_VAR".into(), "default".into()), + ("TARGET_VAR".into(), "target_specific".into()), + ("CONFLICT".into(), "target_specific".into()), + ] + .into_iter() + .collect() + ); + assert_eq!( + dev_env.runtime_inputs, + vec!["default".into(), "target_specific".into()] + .into_iter() + .collect() + ); + + Ok(()) + } + + #[test] + fn build_input_merge() -> eyre::Result<()> { + let target = format!("{}", target_lexicon::HOST); + let data = JavascriptDependencyData { + default: JavascriptDependencyTargetData { + build_inputs: vec!["default".into()].into_iter().collect(), + ..Default::default() + }, + targets: { + let mut map = HashMap::default(); + map.insert( + target, + JavascriptDependencyTargetData { + build_inputs: vec!["target_specific".into()].into_iter().collect(), + ..Default::default() + }, + ); + map + }, + }; + let merged = data.build_inputs(); + assert_eq!( + merged, + vec!["default".into(), "target_specific".into()] + .into_iter() + .collect() + ); + Ok(()) + } + + #[test] + fn environment_variables_merge() -> eyre::Result<()> { + let target = format!("{}", target_lexicon::HOST); + let data = JavascriptDependencyData { + default: JavascriptDependencyTargetData { + environment_variables: vec![ + ("DEFAULT_VAR".into(), "default".into()), + ("CONFLICT".into(), "default".into()), + ] + .into_iter() + .collect(), + ..Default::default() + }, + targets: { + let mut map = HashMap::default(); + map.insert( + target, + JavascriptDependencyTargetData { + environment_variables: vec![ + ("TARGET_VAR".into(), "target_specific".into()), + ("CONFLICT".into(), "target_specific".into()), + ] + .into_iter() + .collect(), + ..Default::default() + }, + ); + map + }, + }; + let merged = data.environment_variables(); + assert_eq!( + merged, + vec![ + ("DEFAULT_VAR".into(), "default".into()), + ("TARGET_VAR".into(), "target_specific".into()), + ("CONFLICT".into(), "target_specific".into()), + ] + .into_iter() + .collect() + ); + Ok(()) + } + + #[test] + fn runtime_input_merge() -> eyre::Result<()> { + let target = format!("{}", target_lexicon::HOST); + let data = JavascriptDependencyData { + default: JavascriptDependencyTargetData { + runtime_inputs: vec!["default".into()].into_iter().collect(), + ..Default::default() + }, + targets: { + let mut map = HashMap::default(); + map.insert( + target, + JavascriptDependencyTargetData { + runtime_inputs: vec!["target_specific".into()].into_iter().collect(), + ..Default::default() + }, + ); + map + }, + }; + let merged = data.runtime_inputs(); + assert_eq!( + merged, + vec!["default".into(), "target_specific".into()] + .into_iter() + .collect() + ); + Ok(()) + } +} diff --git a/src/dependency_registry/mod.rs b/src/dependency_registry/mod.rs index bd53cee..6a6dc7d 100644 --- a/src/dependency_registry/mod.rs +++ b/src/dependency_registry/mod.rs @@ -12,9 +12,13 @@ use tokio::{ }; use xdg::{BaseDirectories, BaseDirectoriesError}; -use self::rust::RustDependencyRegistryData; +use javascript::JavascriptDependencyRegistryData; +use rust::RustDependencyRegistryData; +use go::GoDependencyRegistryData; +pub(crate) mod javascript; pub(crate) mod rust; +pub(crate) mod go; const DEPENDENCY_REGISTRY_REMOTE_URL: &str = "https://registry.riff.determinate.systems/riff-registry.json"; @@ -220,5 +224,10 @@ pub struct DependencyRegistryData { #[derive(Deserialize, Default, Clone, Debug)] pub struct DependencyRegistryLanguageData { + #[serde(default)] pub(crate) rust: RustDependencyRegistryData, + #[serde(default)] + pub(crate) javascript: JavascriptDependencyRegistryData, + #[serde(default)] + pub(crate) go: GoDependencyRegistryData, } diff --git a/src/dev_env.rs b/src/dev_env.rs index 35b85a9..58550bb 100644 --- a/src/dev_env.rs +++ b/src/dev_env.rs @@ -1,20 +1,27 @@ //! The developer environment setup. use std::collections::{HashMap, HashSet}; -use std::path::Path; +use std::io::Cursor; +use std::path::{Component, Path}; +use crate::dependency_registry::DependencyRegistry; +use crate::metadata::go::GoPackage; +use crate::metadata::{javascript::PackageJson, rust::CargoMetadata}; +use crate::spinner::SimpleSpinner; use eyre::{eyre, WrapErr}; use itertools::Itertools; use owo_colors::OwoColorize; +use tokio::fs::File; +use tokio::io::AsyncReadExt; use tokio::process::Command; - -use crate::cargo_metadata::CargoMetadata; -use crate::dependency_registry::DependencyRegistry; -use crate::spinner::SimpleSpinner; +use walkdir::WalkDir; +use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)] pub enum DetectedLanguage { Rust, + Javascript, + Go, } #[derive(Debug, Clone)] @@ -64,18 +71,26 @@ impl<'a> DevEnvironment<'a> { pub async fn detect(&mut self, project_dir: &Path) -> color_eyre::Result<()> { if project_dir.join("Cargo.toml").exists() { self.detected_languages.insert(DetectedLanguage::Rust); - self.add_deps_from_cargo(project_dir).await?; - Ok(()) - } else { + self.add_deps_from_cargo_toml(project_dir).await?; + } else if project_dir.join("go.mod").exists() { + self.detected_languages.insert(DetectedLanguage::Go); + self.add_deps_from_go_mod(project_dir).await?; + } else if project_dir.join("package.json").exists() { + self.detected_languages.insert(DetectedLanguage::Javascript); + self.add_deps_from_package_json(project_dir).await?; + } + if self.detected_languages.is_empty() { Err(eyre!( "'{}' does not contain a project recognized by Riff.", project_dir.display() )) + } else { + Ok(()) } } #[tracing::instrument(skip_all, fields(project_dir = %project_dir.display()))] - async fn add_deps_from_cargo(&mut self, project_dir: &Path) -> color_eyre::Result<()> { + async fn add_deps_from_cargo_toml(&mut self, project_dir: &Path) -> color_eyre::Result<()> { tracing::debug!("Adding Cargo dependencies..."); let mut cargo_metadata_command = Command::new("cargo"); @@ -205,6 +220,249 @@ impl<'a> DevEnvironment<'a> { Ok(()) } + + async fn add_deps_from_go_mod(&mut self, project_dir: &Path) -> color_eyre::Result<()> { + let mut cmd = Command::new("nix"); + let cmd_print = "go list".cyan(); + cmd.current_dir(project_dir); + cmd.args(&[ + "--extra-experimental-features", + "flakes nix-command", + "run", + "nixpkgs#go", + "--", + "list", + "-deps", + "-json", + "...", + ]); + + let spinner = SimpleSpinner::new_with_message(Some(&format!("Running `{cmd_print}`"))) + .context("Failed to construct progress spinner")?; + + let output = cmd.output().await.unwrap_or_else(|e| { + eprintln!( + "\ + Could not execute `{cmd_print}`. Is Nix installed?\n\n\ + Get instructions for installing Nix: {nix_install_url}\n\n\ + Underlying error: {err} + ", + nix_install_url = "https://nixos.org/download.html".blue().underline(), + err = e.red(), + ); + std::process::exit(1); + }); + + spinner.finish_and_clear(); + + /* In some cases, like podman, things that don't work are + * drawn in and go list exits with code 1, but we still get a + * usable package list. + * + * TODO: work out if there's a better way to deal with that than + * ignoring failure completely. + + if !output.status.success() { + return Err(eyre!( + "{cmd_print} exited with code {exit_code}:\n{output}", + exit_code = output + .status + .code() + .map(|x| x.to_string()) + .unwrap_or("unknown".to_string()), + // TODO: don't return just the UTF8 decode error if it's not valid UTF8 + output = std::str::from_utf8(&output.stderr)? + )); + } + */ + + let mut packages: Vec = Vec::new(); + + // We have a bunch of JSON objects which are simply + // concatenated with newlines, so we can't deserialise this as + // a Vec directly. Instead, we'll repeatedly try to + // deserialise a single package's metadata, until we reach the + // end of the output. + let mut de = serde_json::Deserializer::from_reader(Cursor::new(&output.stdout)); + loop { + match GoPackage::deserialize(&mut de) { + Ok(meta) => packages.push(meta), + // TODO: does this mean that we'll ignore trailing garbage? + Err(e) if e.is_eof() => { break; }, + Err(e) => Err(e)?, + } + } + + let language_registry = self.registry.language().await.clone(); + language_registry.go.default.apply(self); + for package in packages { + if let Some(dep_config) = language_registry.go.dependencies.get(package.import_path.as_str()) { + tracing::debug!( + package_name = %package.import_path, + "build-inputs" = %dep_config.build_inputs().iter().join(", "), + "environment-variables" = %dep_config.environment_variables().iter().map(|(k, v)| format!("{k}={v}")).join(", "), + "runtime-inputs" = %dep_config.runtime_inputs().iter().join(", "), + "Detected known crate information" + ); + dep_config.clone().apply(self); + } + } + + Ok(()) + } + + #[tracing::instrument(skip_all, fields(project_dir = %project_dir.display()))] + async fn add_deps_from_package_json(&mut self, project_dir: &Path) -> color_eyre::Result<()> { + tracing::debug!("Adding Javascript dependencies..."); + + // Infer offline-ness from our stored registry + // if self.registry.offline() { + let mut yarn_install_command = Command::new("nix"); + yarn_install_command.args(&["--extra-experimental-features"]); + yarn_install_command.args(&["flakes nix-command"]); + yarn_install_command.arg("shell"); + yarn_install_command.arg("nixpkgs#nodejs"); + yarn_install_command.arg("nixpkgs#yarn"); + yarn_install_command.arg("-c"); + yarn_install_command.arg("yarn"); + yarn_install_command.arg("install"); + + tracing::trace!(command = ?yarn_install_command.as_std(), "Running"); + let spinner = SimpleSpinner::new_with_message(Some(&format!( + "Running `{yarn_install}`", + yarn_install = "nix run nixpkgs#yarn -- install".cyan() + ))) + .context("Failed to construct progress spinner")?; + + let yarn_install_output = match yarn_install_command.output().await { + Ok(output) => output, + Err(err) => { + let err_msg = format!( + "\ + Could not execute `{yarn_install}`. . Is `{nix}` installed?\n\n\ + Get instructions for installing Nix: {nix_install_url}\ + ", + yarn_install = "nix run nixpkgs#yarn -- install".cyan(), + nix = "nix".cyan(), + nix_install_url = "https://nixos.org/download.html".blue().underline(), + ); + eprintln!("{err_msg}\n\nUnderlying error:\n{err}", err = err.red()); + std::process::exit(1); + } + }; + + spinner.finish_and_clear(); + + if !yarn_install_output.status.success() { + return Err(eyre!( + "`nix run nixpkgs#yarn -- install` exited with code {}:\n{}", + yarn_install_output + .status + .code() + .map(|x| x.to_string()) + .unwrap_or_else(|| "unknown".to_string()), + std::str::from_utf8(&yarn_install_output.stderr)?, + )); + } + // } + + tracing::debug!(fresh = %self.registry.fresh(), "Cache freshness"); + let language_registry = self.registry.language().await.clone(); + language_registry.javascript.default.apply(self); + + let walker = WalkDir::new(&project_dir) + .follow_links(false) + .same_file_system(true); + + for entry in walker { + let entry = + entry.wrap_err_with(|| eyre!("Could not walk `{}`", project_dir.display()))?; + if entry.path().components().any(|v| { + v == Component::Normal("tests".as_ref()) || v == Component::Normal("test".as_ref()) + }) { + continue; + } + if entry.path().components().last() != Some(Component::Normal("package.json".as_ref())) + { + continue; + } + tracing::trace!(path = %entry.path().display(), "Walking"); + let mut package_json = File::open(entry.path()).await?; + let package_json_path = entry.path(); + let mut buf = String::default(); + package_json + .read_to_string(&mut buf) + .await + .wrap_err_with(|| eyre!("Could not parse `{}`", package_json_path.display()))?; + + let package_json: PackageJson = serde_json::from_str(&buf).wrap_err_with(|| { + eyre!("Error parsing `{}` as JSON", package_json_path.display()) + })?; + + let riff_config = package_json.config.and_then(|v| v.riff); + + if let Some(ref name) = &package_json.name { + if let Some(dep_config) = + language_registry.javascript.dependencies.get(name.as_str()) + { + tracing::debug!( + package_name = %name, + "build-inputs" = %dep_config.build_inputs().iter().join(", "), + "environment-variables" = %dep_config.environment_variables().iter().map(|(k, v)| format!("{k}={v}")).join(", "), + "runtime-inputs" = %dep_config.runtime_inputs().iter().join(", "), + "Detected known package information" + ); + dep_config.clone().apply(self); + } + } + + if let Some(dep_config) = riff_config { + tracing::debug!( + package = %package_json.name.unwrap_or_default(), + "build-inputs" = %dep_config.build_inputs().iter().join(", "), + "environment-variables" = %dep_config.environment_variables().iter().map(|(k, v)| format!("{k}={v}")).join(", "), + "runtime-inputs" = %dep_config.runtime_inputs().iter().join(", "), + "Detected `config.riff` in `package.json`" + ); + dep_config.apply(self); + } + } + + eprintln!( + "{check} {lang}: {colored_inputs}{maybe_colored_envs}", + check = "✓".green(), + lang = "⬢ javascript".bold().green(), + colored_inputs = { + let mut sorted_build_inputs = self + .build_inputs + .union(&self.runtime_inputs) + .collect::>(); + sorted_build_inputs.sort(); + sorted_build_inputs.iter().map(|v| v.cyan()).join(", ") + }, + maybe_colored_envs = { + if !self.environment_variables.is_empty() { + let mut sorted_environment_variables = self + .environment_variables + .iter() + .map(|(k, _)| k) + .collect::>(); + sorted_environment_variables.sort(); + format!( + " ({})", + sorted_environment_variables + .iter() + .map(|v| v.green()) + .join(", ") + ) + } else { + "".to_string() + } + } + ); + + Ok(()) + } } pub(crate) trait DevEnvironmentAppliable { diff --git a/src/flake-template.inc b/src/flake-template.inc index 21e3894..752706e 100644 --- a/src/flake-template.inc +++ b/src/flake-template.inc @@ -8,7 +8,7 @@ forAllSystems = f: genAttrs allSystems (system: f rec {{ inherit system; - pkgs = import nixpkgs {{ inherit system; }}; + pkgs = import nixpkgs {{ inherit system; config.allowUnfree = true; }}; lib = pkgs.lib; }}); in diff --git a/src/flake_generator.rs b/src/flake_generator.rs index 9f7a186..544f076 100644 --- a/src/flake_generator.rs +++ b/src/flake_generator.rs @@ -32,7 +32,7 @@ pub async fn generate_flake_from_project_dir( let err_msg = format!( "\ `{colored_project_dir}` doesn't contain a project recognized by Riff.\n\ - Try running `{riff_shell}` in a Rust project directory.\ + Try running `{riff_shell}` in a Rust or Javascript project directory.\ ", colored_project_dir = &project_dir.display().to_string().green(), riff_shell = "riff shell".cyan(), diff --git a/src/main.rs b/src/main.rs index bc191c8..fdb943b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ -mod cargo_metadata; mod cmds; mod dependency_registry; mod dev_env; mod flake_generator; +mod metadata; mod nix_dev_env; mod spinner; mod telemetry; diff --git a/src/metadata/go.rs b/src/metadata/go.rs new file mode 100644 index 0000000..f7bc62b --- /dev/null +++ b/src/metadata/go.rs @@ -0,0 +1,15 @@ +// use std::path::PathBuf; + +#[derive(Debug, serde::Deserialize)] +pub(crate) struct GoPackage { + // #[serde(rename = "Dir")] + // pub(crate) dir: PathBuf, + #[serde(rename = "ImportPath")] + pub(crate) import_path: String, + // #[serde(rename = "CgoCFLAGS")] + // pub(crate) cgo_cflags: Option>, + // #[serde(rename = "CgoLDFLAGS")] + // pub(crate) cgo_ldflags: Option>, + // #[serde(rename = "CgoPkgConfig")] + // pub(crate) cgo_pkg_config: Option>, +} diff --git a/src/metadata/javascript.rs b/src/metadata/javascript.rs new file mode 100644 index 0000000..ed38cc7 --- /dev/null +++ b/src/metadata/javascript.rs @@ -0,0 +1,12 @@ +use crate::dependency_registry::javascript::JavascriptDependencyData; + +#[derive(serde::Deserialize)] +pub struct PackageJson { + pub name: Option, + pub config: Option, +} + +#[derive(serde::Deserialize)] +pub struct PackageJsonConfig { + pub riff: Option, +} diff --git a/src/metadata/mod.rs b/src/metadata/mod.rs new file mode 100644 index 0000000..9389dc7 --- /dev/null +++ b/src/metadata/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod javascript; +pub(crate) mod rust; +pub(crate) mod go; diff --git a/src/cargo_metadata.rs b/src/metadata/rust.rs similarity index 100% rename from src/cargo_metadata.rs rename to src/metadata/rust.rs