Skip to content

Commit

Permalink
Merge pull request #945 from charlespierce/scoped_link
Browse files Browse the repository at this point in the history
[Medium] Add extra directory when linking scoped package
  • Loading branch information
chriskrycho authored Feb 22, 2021
2 parents 01f6e04 + 9787385 commit 3aae4cd
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 17 deletions.
17 changes: 17 additions & 0 deletions crates/volta-core/src/run/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,23 @@ impl PackageInstallCommand {
})
}

pub fn for_npm_link<A, S>(args: A, platform: Platform, name: String) -> Fallible<Self>
where
A: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let installer = DirectInstall::with_name(PackageManager::Npm, name)?;

let mut command = create_command("npm");
command.args(args);

Ok(PackageInstallCommand {
command,
installer,
platform,
})
}

/// Adds or updates environment variables that the command will use
pub fn envs<E, K, V>(&mut self, envs: E)
where
Expand Down
2 changes: 1 addition & 1 deletion crates/volta-core/src/run/npm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub(super) fn command(args: &[OsString], session: &mut Session) -> Fallible<Exec
CommandArg::Intercepted(InterceptedCommand::Link(link)) => {
// For link commands, only intercept if a platform exists
if let Some(platform) = Platform::current(session)? {
return link.executor(platform);
return link.executor(platform, current_project_name(session));
}
}
CommandArg::Intercepted(InterceptedCommand::Unlink) => {
Expand Down
12 changes: 8 additions & 4 deletions crates/volta-core/src/run/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,12 +345,16 @@ pub struct LinkArgs<'a> {
}

impl<'a> LinkArgs<'a> {
pub fn executor(self, platform: Platform) -> Fallible<Executor> {
pub fn executor(self, platform: Platform, project_name: Option<String>) -> Fallible<Executor> {
if self.tools.is_empty() {
// If no tools are specified, then this is a bare link command, linking the current
// project as a global package. We treat this exactly like a global install
PackageInstallCommand::new(self.common_args, platform, PackageManager::Npm)
.map(Into::into)
// project as a global package. We treat this like a global install except we look up
// the name from the current directory first.
match project_name {
Some(name) => PackageInstallCommand::for_npm_link(self.common_args, platform, name),
None => PackageInstallCommand::new(self.common_args, platform, PackageManager::Npm),
}
.map(Into::into)
} else {
// If there are tools specified, then this represents a command to link a global
// package into the current project. We handle each tool separately to support Volta's
Expand Down
67 changes: 55 additions & 12 deletions crates/volta-core/src/tool/package/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub struct Package {

impl Package {
pub fn new(name: String, version: VersionSpec) -> Fallible<Self> {
let staging = setup_staging_directory(PackageManager::Npm)?;
let staging = setup_staging_directory(PackageManager::Npm, NeedsScope::No)?;

Ok(Package {
name,
Expand Down Expand Up @@ -123,16 +123,34 @@ impl Display for Package {
///
/// Provides methods to simplify installing into a staging directory and then moving that install
/// into the proper location after it is complete.
///
/// Note: We don't always know the name of the package up-front, as the install could be from a
/// tarball or a git coordinate. If we do know ahead of time, then we can skip looking it up
pub struct DirectInstall {
staging: TempDir,
manager: PackageManager,
name: Option<String>,
}

impl DirectInstall {
pub fn new(manager: PackageManager) -> Fallible<Self> {
let staging = setup_staging_directory(manager)?;
let staging = setup_staging_directory(manager, NeedsScope::No)?;

Ok(DirectInstall {
staging,
manager,
name: None,
})
}

Ok(DirectInstall { staging, manager })
pub fn with_name(manager: PackageManager, name: String) -> Fallible<Self> {
let staging = setup_staging_directory(manager, name.contains('/').into())?;

Ok(DirectInstall {
staging,
manager,
name: Some(name),
})
}

pub fn setup_command(&self, command: &mut Command) {
Expand All @@ -141,16 +159,20 @@ impl DirectInstall {
}

pub fn complete_install(self, image: &Image) -> Fallible<()> {
let name = self
.manager
.get_installed_package(self.staging.path().to_owned())
let DirectInstall {
staging,
name,
manager,
} = self;

let name = name
.or_else(|| manager.get_installed_package(staging.path().to_owned()))
.ok_or(ErrorKind::InstalledPackageNameError)?;
let manifest =
configure::parse_manifest(&name, self.staging.path().to_owned(), self.manager)?;
let manifest = configure::parse_manifest(&name, staging.path().to_owned(), manager)?;

persist_install(&name, &manifest.version, self.staging.path())?;
link_package_to_shared_dir(&name, self.manager)?;
configure::write_config_and_shims(&name, &manifest, image, self.manager)
persist_install(&name, &manifest.version, staging.path())?;
link_package_to_shared_dir(&name, manager)?;
configure::write_config_and_shims(&name, &manifest, image, manager)
}
}

Expand Down Expand Up @@ -209,19 +231,40 @@ impl InPlaceUpgrade {
}
}

#[derive(Clone, Copy, PartialEq)]
enum NeedsScope {
Yes,
No,
}

impl From<bool> for NeedsScope {
fn from(value: bool) -> Self {
if value {
NeedsScope::Yes
} else {
NeedsScope::No
}
}
}

/// Create the temporary staging directory we will use to install and ensure expected
/// subdirectories exist within it
fn setup_staging_directory(manager: PackageManager) -> Fallible<TempDir> {
fn setup_staging_directory(manager: PackageManager, needs_scope: NeedsScope) -> Fallible<TempDir> {
// Workaround to ensure relative symlinks continue to work.
// The final installed location of packages is:
// $VOLTA_HOME/tools/image/packages/{name}/
// To ensure that the temp directory has the same amount of nesting, we use:
// $VOLTA_HOME/tmp/image/packages/{tempdir}/
// This way any relative symlinks will have the same amount of nesting and will remain valid
// even when the directory is persisted.
// We also need to handle the case when the linked package has a scope, which requires another
// level of nesting
let mut staging_root = volta_home()?.tmp_dir().to_owned();
staging_root.push("image");
staging_root.push("packages");
if needs_scope == NeedsScope::Yes {
staging_root.push("scope");
}
create_dir_all(&staging_root).with_context(|| ErrorKind::ContainingDirError {
path: staging_root.clone(),
})?;
Expand Down

0 comments on commit 3aae4cd

Please sign in to comment.