Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find npm in wrong place #152

Open
rhdtl78 opened this issue Mar 5, 2021 · 32 comments
Open

Find npm in wrong place #152

rhdtl78 opened this issue Mar 5, 2021 · 32 comments

Comments

@rhdtl78
Copy link

rhdtl78 commented Mar 5, 2021

Related the document, plugin have to find npm on global scope, but it is not.

Caused by: org.gradle.process.internal.ExecException: A problem occurred starting process 'command 'npm''
at org.gradle.process.internal.DefaultExecHandle.execExceptionFor(DefaultExecHandle.java:241)
...
org.gradle.internal.operations.CurrentBuildOperationPreservingRunnable.run(CurrentBuildOperationPreservingRunnable.java:42)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
...
Caused by: java.io.IOException: Cannot run program "npm" (in directory "~/projects/projectRoot"): error=2, No such file or directory

So, I tried to override npm or node excitable path but no effect

My environment is like below.

os.arch = aarch64
os.name = Mac OS X
os.version = 11.2.2
openjdk version "11.0.10" 2021-01-19 LTS
OpenJDK Runtime Environment Zulu11.45+27-CA (build 11.0.10+9-LTS)
OpenJDK 64-Bit Server VM Zulu11.45+27-CA (build 11.0.10+9-LTS, mixed mode)

node -v
v15.10.0
process.arch: arm64

@bsautel
Copy link
Contributor

bsautel commented Mar 5, 2021

I confirm that by default the plugin uses the npm command available in the PATH and it is how I use it. The integration tests ensure that we do use the globally or locally installed node and npm commands according to the configuration and they run on both Windows, macOS and Linux. You are probably in a really specific case.

You will probably think that my question is stupid but I ask it anyway: is npm available in the PATH in your environment?

@rhdtl78
Copy link
Author

rhdtl78 commented Mar 17, 2021

I confirm that by default the plugin uses the npm command available in the PATH and it is how I use it. The integration tests ensure that we do use the globally or locally installed node and npm commands according to the configuration and they run on both Windows, macOS and Linux. You are probably in a really specific case.

You will probably think that my question is stupid but I ask it anyway: is npm available in the PATH in your environment?

Sorry for late reply.

I'm using node installed by nvm.
I can us node globally, and nvm node path is also exported.

@bsautel
Copy link
Contributor

bsautel commented Mar 19, 2021

Ok for node, but is npm also available in the PATH. If you run it in the same environment (terminal) directly or via Gradle, it should work. Are you sure you are using the same environment when running Gradle?

@rhdtl78
Copy link
Author

rhdtl78 commented Mar 22, 2021

Yeah. I'm sure it is same env. Maybe it caused by architecture. I can build project by Gradle with this plugin on Rosetta2, but not in M1 Native.

Maybe this issue related with this

@bsautel
Copy link
Contributor

bsautel commented Mar 22, 2021

Indeed I did not noticed you are running a Mac M1. But I don't see why the npm command works when you run it from the terminal and not when it is launched by Gradle.

Can someone using a Mac M1 confirm that it the problem comes from the platform?

The issue #154 is not exactly the same problem. You are not asking the plugin to download and install Node.js, and this is what was reported as broken on Mac M1.

@rhdtl78
Copy link
Author

rhdtl78 commented Mar 25, 2021

Sorry for late replay.

Out of curiosity, if you run this with a x64 JVM does it work?

Yeah. I can run on x86 JVM OpenJDK 11 with rosetta2.
Maybe this is the workaround for now. You can close this issue.

@bsautel
Copy link
Contributor

bsautel commented Mar 25, 2021

Ok, so you mean that the issue happens only when running an arm64 JVM. If you run a x86 one using Rosetta 2, you don't encounter the issue, right?

@rhdtl78
Copy link
Author

rhdtl78 commented Mar 25, 2021

exactly.

@bsautel
Copy link
Contributor

bsautel commented Mar 25, 2021

Ok, that's interesting. We use the Gradle exec API to execute node, npm and yarn. So either the issue is in the Gradle exec API implementation but I assume this is more probable that it comes from an issue in the arm64 JVM.

Can you run this class using the x64 JVM and the arm one?

import java.io.IOException;

import static java.nio.charset.StandardCharsets.UTF_8;

public class JavaNpmExec {
    public static void main(String[] args) throws IOException {
        String command = "npm -version";
        Process child = Runtime.getRuntime().exec(command);
        System.out.println(new String(child.getInputStream().readAllBytes(), UTF_8));
        System.err.println(new String(child.getErrorStream().readAllBytes(), UTF_8));
    }
}

If you use Java 11+, you can simply create a JavaNpmExec.java file containing this anywhere on your filesystem and run it without compiling it by using java JavaNpmExec.java.

It runs npm which is expected to be available in the PATH (add it if it is not). It should work with the x64 JVM and probably not with the arm one.

@alexpartsch
Copy link

@bsautel I'm running into the same issue when having download = false set on a Apple M1 Book, running the given Java program with aarch64 on JDK 16:

/tmp % java JavaNpmExec.java
7.6.0


Using a x86 compatible JDK 16 on M1 yields the same:

/tmp % java /tmp/JavaNpmExec.java
7.6.0


@bsautel
Copy link
Contributor

bsautel commented Mar 30, 2021

Ok, thanks for your feedback @alexpartsch. Unfortunately this experiment did not enable us to spot the origin of the issue but that's quite a good news there is not a such big bug in the arm64 JVM!

We still have to find where does the issue come from. Let's check something on the Gradle side.

Gradle 7.0-rc1 was released a few days ago and adds full support of arm64 JVM. They don't talk about exec related issues in the release notes but could someone check whether the issue is still here when using an arm64 JVM with Gradle 7.0-rc1 please?

@michal-kaciuba
Copy link

Hi! Got the same issue on M1. Using Gradle v7.2 and gradle-node-plugin v3.2.1.

@deepy
Copy link
Member

deepy commented Mar 14, 2022

@MKaciuba just to confirm, that's getting Cannot run program "npm" (in directory ... when running with download = false right?

@lonely-lockley
Copy link

Faced this issue on Intel Mac. The funny thing that it stopped working without any external changes. The day before I've launched several builds successfully and today 'Cannot run program "npm"'. Everything works fine from terminal.
Liberica JDK 21, Gradle 8.5, Ventura 13.2.1, plugin version 7.0.1

@lonely-lockley
Copy link

lonely-lockley commented Dec 18, 2023

Could workaround the problem by setting:

node {
    npmCommand = '/usr/local/bin/npm'
}

Adding println System.getenv('PATH') to build.gradle showed that /usr/local/bin IS IN the PATH so the real cause of the problem is still not clear

@deepy
Copy link
Member

deepy commented Dec 19, 2023

Are you using download = true or should it run with local node? And which task is failing?

Assuming it's download = true, can you run your build with --info and see whether nodeSetup and npmSetup reports up-to-date, check in the .gradle folder in the project and see which versions exists under nodejs and npm
Then run ./gradlew nodeSetup npmSetup --rerun-tasks and if the build works after that, see if any new versions appeared under the nodejs and npm folders

@lonely-lockley
Copy link

Download is set to false by default. I tried to run npmInstall

@tzcsx
Copy link

tzcsx commented Mar 19, 2024

I am also experiencing this:

Caused by: net.rubygrapefruit.platform.NativeException: Could not start 'npm'
        at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:27)
        at net.rubygrapefruit.platform.internal.WrapperProcessLauncher.start(WrapperProcessLauncher.java:36)
        at org.gradle.process.internal.ExecHandleRunner.startProcess(ExecHandleRunner.java:122)
        at org.gradle.process.internal.ExecHandleRunner.lambda$run$0(ExecHandleRunner.java:80)
        at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:80)
        at org.gradle.process.internal.ExecHandleRunner.run(ExecHandleRunner.java:79)
        ... 2 more
Caused by: java.io.IOException: Cannot run program "npm" (in directory "/Users/me/dev/project/frontend"): error=2, No such file or directory
        at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25)

Reproducible for the first build run via IntelliJ after system boot. Once this error occurs, it persists for builds outside of IntelliJ, too. Stopping all Gradle daemons with ./gradlew --stop and starting a new build fixes the problem until the next reboot.

I can confirm that Gradle itself sees the correct $PATH, which includes npm, but having poked around in the source I am not sure if NpmExecRunner uses the same path. IntelliJ also sees the correct $PATH, so it should not be related to the shell environment loading https://intellij-support.jetbrains.com/hc/en-us/articles/15268184143890-Shell-Environment-Loading.

When using

node {
  npmCommand = "/Users/me/.nvm/versions/node/v20.11.0/bin/npm"
}

this issue does not occur at all.

Environment
download = false (using default)
Node & NPM via nvm
macOS 14.2 on M2
Java 21.0.2
Gradle 8.6
IntelliJ 2023.3.5

@tzcsx
Copy link

tzcsx commented Mar 20, 2024

Correction: Every build started via IntelliJ UI will fail with this error, if this build creates a new Gradle Daemon instance. If there is a Daemon created by a build outside of IntelliJ, i.e. terminal, the build run from IntelliJ will work, too. However directly triggering npm from IntelliJ UI works and a terminal started inside IntelliJ sees npm, too. So I still think this is unrelated to IntelliJ shell environment loading.

I am unsure whether the Gradle version is a factor. Still investigating.

@deepy
Copy link
Member

deepy commented Mar 20, 2024

@tzcsx ARM mac?
Can you add the output of sysctl sysctl.proc_translated when run through the failing scenario?

var process = new ProcessBuilder("sysctl", "sysctl.proc_translated").inheritIO().start().waitFor();

I suspect you're hitting the issue at: #154 (comment)

@tzcsx
Copy link

tzcsx commented Mar 20, 2024

Yes, ARM mac M2 and download = false. I tried this:

tasks.register<NpmTask>("doStuffWithNpm") {
    args.add("do-stuff")

    val process = ProcessBuilder("sysctl", "sysctl.proc_translated").start()
    val output = BufferedReader(InputStreamReader(process.inputStream))
    process.waitFor()
    println("configure " + System.getProperty("os.arch") + " " + output.readLine());

    doFirst {
        val process = ProcessBuilder("sysctl", "sysctl.proc_translated").start()
        val output = BufferedReader(InputStreamReader(process.inputStream))
        process.waitFor()
        println("doFirst " + System.getProperty("os.arch") + " " + output.readLine());
    }
}

This results in "aarch64 sysctl.proc_translated: 0" for all phases in both the running and failing scenarios.

@DylanLukes
Copy link

DylanLukes commented May 25, 2024

+1 to experiencing this (and also on an M1 Mac), though my npm is at /Users/dylan/.asdf/shims/npm. Works perfectly fine when running gradle from the command line. Inspecting the state of the ProcessBuilder at the point of failure, I can see that /Users/dylan/.asdf/shims/ is indeed on the path, so the shell environment not being present is certainly not the issue.

Even stranger, if I pause the debugger at the point the exception is thrown and run:

new String(new ProcessBuilder("npm", "--version").start().getInputStream().readAllBytes());

...it spits back out the correct version, apparently able to find npm with no problem!


Also, for me this produces 0 in the failing case, and I'm not downloading any npm.

ProcessBuilder("sysctl", "sysctl.proc_translated").inheritIO().start().waitFor();

@DylanLukes
Copy link

DylanLukes commented May 25, 2024

The last handoff point is in executeCommand in NpmExecRunner:

private fun executeCommand(project: ProjectApiHelper, extension: NodeExtension, nodeExecConfiguration: NodeExecConfiguration,
npmExecConfiguration: NpmExecConfiguration,
variantComputer: VariantComputer): ExecResult {
val execConfiguration =
computeExecConfiguration(extension, npmExecConfiguration, nodeExecConfiguration, variantComputer).get()
val execRunner = ExecRunner()
return execRunner.execute(project, extension, execConfiguration)
}
private fun computeExecConfiguration(extension: NodeExtension, npmExecConfiguration: NpmExecConfiguration,
nodeExecConfiguration: NodeExecConfiguration,
variantComputer: VariantComputer): Provider<ExecConfiguration> {
val additionalBinPathProvider = computeAdditionalBinPath(extension, variantComputer)
val executableAndScriptProvider = computeExecutable(extension, npmExecConfiguration, variantComputer)
return zip(additionalBinPathProvider, executableAndScriptProvider)
.map { (additionalBinPath, executableAndScript) ->
val argsPrefix =
if (executableAndScript.script != null) listOf(executableAndScript.script) else listOf()
val args = argsPrefix.plus(nodeExecConfiguration.command)
ExecConfiguration(executableAndScript.executable, args, additionalBinPath,
nodeExecConfiguration.environment, nodeExecConfiguration.workingDir,
nodeExecConfiguration.ignoreExitValue, nodeExecConfiguration.execOverrides)
}
}

But the ExecConfiguration produced there doesn't seem too unusual, except for the additionalBinPaths being spurious:

execConfiguration = {ExecConfiguration@19886} ExecConfiguration(executable=npm, args=[install], additionalBinPaths=[/Users/dylan/IntelliJIDEAProjects/tilia/projects/service/www/.gradle/nodejs/node-v18.17.1-darwin-arm64/bin, /Users/dylan/IntelliJIDEAProjects/tilia/projects/service/www/.gradle/nodejs/node-v18.17.1-darwin-arm64/bin], environment={}, workingDir=null, ignoreExitValue=false, execOverrides=null)
 executable = "npm"
 args = {ArrayList@19897}  size = 1
 additionalBinPaths = {ArrayList@19898}  size = 2
  0 = "/Users/dylan/IntelliJIDEAProjects/tilia/projects/service/www/.gradle/nodejs/node-v18.17.1-darwin-arm64/bin"
  1 = "/Users/dylan/IntelliJIDEAProjects/tilia/projects/service/www/.gradle/nodejs/node-v18.17.1-darwin-arm64/bin"
 environment = {RegularImmutableMap@19836}  size = 0
 workingDir = null
 ignoreExitValue = false
 execOverrides = null

The additionalBinPaths here don't seem be an issue as if I delete them before allowing execution to resume, and this configuration is identical across all configurations (command line, IDEA, IDEA launched from command line).


Manually running a simplified case in the same execution context seems to produce the same issue.

execRunner.execute(project, extension, ExecConfiguration("npm", listOf("--version")))

However, this works:

ProcessBuilder("npm", "--version").start().inputReader().readText()

@DylanLukes
Copy link

DylanLukes commented May 25, 2024

Well, that's interesting. I get this failure when I run IntelliJ by opening it as an app, but if I start IntelliJ from a shell then I have no issues.

cd project-dir
idea .

Not exactly a satisfying workaround, but might point to the root of the issue (in conjunction with it not being a PATH issue...)

Part of what's still bugging me is that this is only happening for npm and not say for uname which runs through the same process launcher ahead of npm. Maybe there's some MacOS security system related thing going on, the difference being one is a trusted system binary and the other isn't...? Running from the command line could confer some entitlements or relaxation of security. But then why does a ProcessBuilder like at the end of my prior comment work just fine?

@DylanLukes
Copy link

DylanLukes commented May 26, 2024

Well, this is interesting...

Screenshot 2024-05-25 at 17 21 42

So, the issue doesn't seem to be finding npm at all.

It appears that setting environment to null here allows execution to proceed normally, though merely emptying it does not.

It's not clear to me exactly why yet, but I've traced it all the way down to a native call (forkAndExec) in the constructor of java.lang.ProcessImpl:

    private ProcessImpl(final byte[] prog,
                final byte[] argBlock, final int argc,
                final byte[] envBlock, final int envc,
                final byte[] dir,
                final int[] fds,
                final boolean forceNullOutputStream,
                final boolean redirectErrorStream)
            throws IOException {

        pid = forkAndExec(launchMechanism.ordinal() + 1,
                          helperpath,
                          prog,
                          argBlock, argc,
                          envBlock, envc,    // <- nulling out envBlock right before call results in success
                          dir,
                          fds,
                          redirectErrorStream);
        processHandle = ProcessHandleImpl.getInternal(pid);

        try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
                initStreams(fds, forceNullOutputStream);
                return null;
            });
        } catch (PrivilegedActionException ex) {
            throw (IOException) ex.getCause();
        }
    }

As one might expect, setting the envBlock to null here results in success running npm and the npmInstall task succeeding without a hitch.

I've got a diff of the environments running IntelliJ normally (from GUI) and with the idea command from within a terminal and tried splicing changes in to see if anything clicks, but of course it doesn't. It really seems like the issue stems from having environment set at all. Even an empty environment in the ProcessBuilder fails. It's null or no go.

This is probably something to do with macOS permission weirdness. Who knows, it might just suddenly start working after an update... still, that a null environment seemingly gets it to work is a bit bizarre.

@DylanLukes
Copy link

DylanLukes commented May 26, 2024

Well, that's out too. In the debugger, at the site of processBuilder.start(), running:

new String(new ProcessBuilder(processBuilder.command).directory(processBuilder.directory).start().getInputStream().readAllBytes())

Produces:

added 157 packages, and audited 158 packages in 588ms

42 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

The only difference between this process builder and the one created by Gradle internals is the explicitly set environment, nothing else. And nulling that environment back to its default state in that one allows execution to continue. I think this is fully ablated, I'm just not sure what to make of the result:

  • Why would explicitly setting the environment behave differently when the Gradle process is running under IntelliJ vs under shell?
  • Why would it matter if I start IntelliJ from the shell? The idea launcher doesn't own the resulting process, launchd does.
  • Why can npm be found when the environment is inherited, but not when it's explicit set to exactly the same dict (I checked)?

@DylanLukes
Copy link

DylanLukes commented May 26, 2024

Well, in the end I think this issue is not at all related to gradle-node-plugin in particular, and couldn't be fixed here. In an empty project:

tasks.register("mystery") {
    val pb = ProcessBuilder("lua", "--version")
    pb.environment().clear()
    pb.environment().putAll(System.getenv())
    val str = pb.start().inputStream.readAllBytes().decodeToString()
    println(str)
}

This also fails (lua is not an asdf shim but also not on the default path). Things that are on the default system path do work, such as env... which reports back the full PATH I would expect, so I'm flummoxed.

@tzcsx
Copy link

tzcsx commented Jun 3, 2024

I am still running into this every day. The first build of the day needs to be started outside of Intellij in order to produce a working Gradle Daemon process which successive Intellij builds will then use.

@DylanLukes You've dug deep there, thanks. So it looks like this is either a Gradle or Intellij issue, but nothing @deepy can look into. Could you escalate this in a Gradle ticket? The Gradle team should be able to decide if this is related to Gradle itself or if it is a problem with Intellij's MacOS init logic.

@deepy
Copy link
Member

deepy commented Jun 3, 2024

Following the investigation I think this sounds like it could be related to gradle/gradle#10483 (comment) and https://youtrack.jetbrains.com/issue/IDEA-334183

Which to my understanding means that if you're on Java 21 PATH changes won't work and the ideal workaround right now would be starting IDEA from your shell

i.e. PATH changes will look like they work, but won't

In short this is a bit of a headache and means I need to run cross-version tests for Java as well
Which I guess means a new test suite as the serial time on a beefy machine is right now 3 hours

@tzcsx
Copy link

tzcsx commented Jun 3, 2024

@deepy Good find! I can confirm that either building with Java 17 instead of Java 21 or using Java 21 and starting Intellij from the Terminal via open -a IntelliJ\ IDEA will work.

@namero999
Copy link

Had the same problem (running 21 in idea) and ./gradlew --stop and then re-running the tasks seems to work.

In general, is a fix being worked on? Is there even an in-principle solution to this problem that can be pursued?

@deepy
Copy link
Member

deepy commented Oct 4, 2024

There's a mix of issues in this one
For the rosetta issue that's Gradle treating a rosetta and non-rosetta daemon as equal, we've got a workaround for that scenario which uses the x64 version and rosetta emulation (which is slower). But that needs to be solved in Gradle, and we track it in #154
For the path issue I think that's entirely an IDEA thing, Gradle might be able to work around it but there's very little we can do about it in the plugin. The upstream issues are linked in #152 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants