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

Relative paths are relative to current project locally and in Cloud #10660

Merged
merged 24 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

- [Implemented in-memory and database mixed `Decimal` column
comparisons.][10614]
- [Relative paths are now resolved relative to the project location, also in the
Cloud.][10660]

[10614]: https://github.com/enso-org/enso/pull/10614
[10660]: https://github.com/enso-org/enso/pull/10660

# Enso 2023.3

Expand Down
1 change: 0 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3860,7 +3860,6 @@ pkgStdLibInternal := Def.inputTask {
if (generateIndex) {
val stdlibStandardRoot = root / "lib" / standardNamespace
DistributionPackage.indexStdLib(
libMajor = stdlibStandardRoot,
libName = stdlibStandardRoot / lib,
stdLibVersion = defaultDevEnsoVersion,
ensoVersion = defaultDevEnsoVersion,
Expand Down
4 changes: 4 additions & 0 deletions build/build/src/enso.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl BuiltEnso {

pub async fn run_benchmarks(&self, opt: BenchmarkOptions) -> Result {
let filename = format!("enso{}", if TARGET_OS == OS::Windows { ".exe" } else { "" });
let base_working_directory = self.paths.repo_root.test.benchmarks.try_parent()?;
let enso = self
.paths
.repo_root
Expand All @@ -88,6 +89,7 @@ impl BuiltEnso {
.join(filename);
let benchmarks = Command::new(&enso)
.args(["--jvm", "--run", self.paths.repo_root.test.benchmarks.as_str()])
.current_dir(base_working_directory)
.set_env(ENSO_BENCHMARK_TEST_DRY_RUN, &Boolean::from(opt.dry_run))?
.run_ok()
.await;
Expand All @@ -96,10 +98,12 @@ impl BuiltEnso {

pub fn run_test(&self, test_path: impl AsRef<Path>, ir_caches: IrCaches) -> Result<Command> {
let mut command = self.cmd()?;
let base_working_directory = test_path.try_parent()?;
command
.arg(ir_caches)
.arg("--run")
.arg(test_path.as_ref())
.current_dir(base_working_directory)
// This flag enables assertions in the JVM. Some of our stdlib tests had in the past
// failed on Graal/Truffle assertions, so we want to have them triggered.
.set_env(JAVA_OPTS, &ide_ci::programs::java::Option::EnableAssertions.as_ref())?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,15 @@ type Enso_File
directory.
current_working_directory : Enso_File
current_working_directory =
Enso_File.cloud_project_parent_directory . if_nothing Enso_File.root

## PRIVATE
The parent directory containing the currently open project if in the
Cloud, or `Nothing` if running locally.
cloud_project_parent_directory : Enso_File | Nothing
cloud_project_parent_directory =
path = Environment.get "ENSO_CLOUD_PROJECT_DIRECTORY_PATH"
if path.is_nothing then Enso_File.root else
Enso_File.new path
path.if_not_nothing <| Enso_File.new path

## PRIVATE
asset_type self -> Enso_Asset_Type =
Expand Down
23 changes: 22 additions & 1 deletion distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import project.Data.Time.Date_Time.Date_Time
import project.Data.Vector.Vector
import project.Enso_Cloud.Data_Link.Data_Link
import project.Enso_Cloud.Data_Link_Helpers
import project.Enso_Cloud.Enso_File.Enso_File
import project.Error.Error
import project.Errors.Common.Dry_Run_Operation
import project.Errors.Common.Type_Error
Expand Down Expand Up @@ -61,6 +62,10 @@ type File

Creates a new file object, pointing to the given path.

Relative paths are resolved relative to the directory containing the
currently running workflow. Thus, if the workflow is running in the Cloud,
the relative paths will be resolved to Cloud files.

Arguments:
- path: The path to the file that you want to create, or a file itself. The
latter is a no-op.
Expand All @@ -74,7 +79,7 @@ type File
example_new = File.new Examples.csv_path
new : (Text | File) -> Any ! Illegal_Argument
new path = case path of
_ : Text -> if path.contains "://" . not then get_file path else
_ : Text -> if path.contains "://" . not then resolve_path path else
protocol = path.split "://" . first
file_system = FileSystemSPI.get_type protocol False
if file_system.is_nothing then Error.throw (Illegal_Argument.Error "Unsupported protocol "+protocol) else
Expand Down Expand Up @@ -860,6 +865,22 @@ get_cwd = @Builtin_Method "File.get_cwd"
get_file : Text -> File
get_file path = @Builtin_Method "File.get_file"

## PRIVATE
Resolves the given path to a corresponding file location.

If the provided path is relative, the behaviour depends on the context:
- if the project is running in the Cloud, the path is resolved to a Cloud file,
relative to the project's location.
- if running locally, the path is resolved to a local file, relative to the
current working directory.
resolve_path (path : Text) -> File | Enso_File =
local_file = get_file path
# Absolute files always resolve to themselves.
if local_file.is_absolute then local_file else
case Enso_File.cloud_project_parent_directory of
Nothing -> local_file
base_cloud_directory -> base_cloud_directory / path

## PRIVATE
get_child_widget : File -> Widget
get_child_widget file =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ class LauncherRunner(
projectManager.findProject(currentWorkingDirectory).get
}

val version = resolveVersion(versionOverride, inProject)
val version = resolveVersion(versionOverride, inProject)
val workingDirectory = workingDirectoryForRunner(inProject, None)
val arguments = inProject match {
case Some(project) =>
val projectPackagePath =
Expand All @@ -68,6 +69,7 @@ class LauncherRunner(
version,
arguments ++ setLogLevelArgs(logLevel, logMasking)
++ additionalArguments,
workingDirectory = workingDirectory,
connectLoggerIfAvailable = true
)
}
Expand Down Expand Up @@ -109,6 +111,10 @@ class LauncherRunner(
else projectManager.findProject(actualPath).get
val version = resolveVersion(versionOverride, project)

// The engine is started in the directory containing the project, or the standalone script.
val workingDirectory =
workingDirectoryForRunner(project, Some(actualPath))

val arguments =
if (projectMode) Seq("--run", actualPath.toString)
else
Expand All @@ -127,10 +133,24 @@ class LauncherRunner(
version,
arguments ++ setLogLevelArgs(logLevel, logMasking)
++ additionalArguments,
workingDirectory = workingDirectory,
connectLoggerIfAvailable = true
)
}

private def workingDirectoryForRunner(
inProject: Option[Project],
scriptPath: Option[Path]
): Option[Path] = {
// The path of the project or standalone script that is being run.
val baseDirectory = inProject match {
case Some(project) => Some(project.path)
case None => scriptPath
}

baseDirectory.map(p => p.toAbsolutePath.normalize().getParent)
}

private def setLogLevelArgs(
level: Level,
logMasking: Boolean
Expand Down Expand Up @@ -190,7 +210,12 @@ class LauncherRunner(
}

(
RunSettings(version, arguments, connectLoggerIfAvailable = false),
RunSettings(
version,
arguments,
workingDirectory = None,
connectLoggerIfAvailable = false
),
whichEngine
)
}
Expand Down Expand Up @@ -239,6 +264,7 @@ class LauncherRunner(
version,
arguments ++ setLogLevelArgs(logLevel, logMasking)
++ additionalArguments,
workingDirectory = None,
connectLoggerIfAvailable = true
)
}
Expand Down Expand Up @@ -286,6 +312,7 @@ class LauncherRunner(
version,
arguments ++ setLogLevelArgs(logLevel, logMasking)
++ additionalArguments,
workingDirectory = None,
connectLoggerIfAvailable = true
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest with FlakySpec {
val runSettings = RunSettings(
SemVer.of(0, 0, 0),
Seq("arg1", "--flag2"),
workingDirectory = None,
connectLoggerIfAvailable = true
)
val jvmOptions = Seq(("locally-added-options", "value1"))
Expand Down Expand Up @@ -243,6 +244,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest with FlakySpec {
.get

outsideProject.engineVersion shouldEqual version
outsideProject.workingDirectory shouldEqual Some(projectPath.getParent)
outsideProject.runnerArguments.mkString(" ") should
(include(s"--in-project $normalizedPath") and include("--repl"))

Expand All @@ -258,6 +260,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest with FlakySpec {
.get

insideProject.engineVersion shouldEqual version
insideProject.workingDirectory shouldEqual Some(projectPath.getParent)
insideProject.runnerArguments.mkString(" ") should
(include(s"--in-project $normalizedPath") and include("--repl"))

Expand Down Expand Up @@ -304,6 +307,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest with FlakySpec {
.get

runSettings.engineVersion shouldEqual version
runSettings.workingDirectory shouldEqual Some(projectPath.getParent)
val commandLine = runSettings.runnerArguments.mkString(" ")
commandLine should include(s"--interface ${options.interface}")
commandLine should include(s"--rpc-port ${options.rpcPort}")
Expand Down Expand Up @@ -346,6 +350,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest with FlakySpec {
.get

outsideProject.engineVersion shouldEqual version
outsideProject.workingDirectory shouldEqual Some(projectPath.getParent)
outsideProject.runnerArguments.mkString(" ") should
include(s"--run $normalizedPath")

Expand Down Expand Up @@ -443,6 +448,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest with FlakySpec {
.get

runSettings.engineVersion shouldEqual version
runSettings.workingDirectory shouldEqual Some(projectPath.getParent)
runSettings.runnerArguments.mkString(" ") should
(include(s"--run $normalizedFilePath") and
include(s"--in-project $normalizedProjectPath"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import com.oracle.truffle.api.source.Source;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
Expand Down Expand Up @@ -56,6 +58,7 @@
import org.enso.interpreter.runtime.util.TruffleFileSystem;
import org.enso.librarymanager.ProjectLoadingFailure;
import org.enso.librarymanager.resolved.LibraryRoot;
import org.enso.logger.masking.MaskedPath$;
import org.enso.pkg.Package;
import org.enso.pkg.PackageManager;
import org.enso.pkg.QualifiedName;
Expand Down Expand Up @@ -163,6 +166,7 @@ public void initialize() {
PackageManager<TruffleFile> packageManager = new PackageManager<>(fs);

Optional<TruffleFile> projectRoot = OptionsHelper.getProjectRoot(environment);
checkWorkingDirectory(projectRoot);
Optional<Package<TruffleFile>> projectPackage =
projectRoot.map(
file ->
Expand Down Expand Up @@ -206,6 +210,28 @@ public void initialize() {
}
}

/** Checks if the working directory is as expected and reports a warning if not. */
private void checkWorkingDirectory(Optional<TruffleFile> maybeProjectRoot) {
if (maybeProjectRoot.isPresent()) {
var root = maybeProjectRoot.get();
var parent = root.getAbsoluteFile().normalize().getParent();
var cwd = environment.getCurrentWorkingDirectory().getAbsoluteFile().normalize();
try {
if (!cwd.isSameFile(parent)) {
var maskedPath = MaskedPath$.MODULE$.apply(Path.of(parent.toString()));
logger.log(
Level.WARNING,
"Initializing the context in a different working directory than the one containing"
+ " the project root. This may lead to relative paths not behaving as advertised"
+ " by `File.new`. Please run the engine inside of `{}` directory.",
maskedPath);
}
} catch (IOException e) {
logger.severe("Error checking working directory: " + e.getMessage());
}
}
}

/**
* @param node the location of context access. Pass {@code null} if not in a node.
* @return the proper context instance for the current {@link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ package org.enso.runtimeversionmanager.runner
import com.typesafe.scalalogging.Logger
import org.enso.process.WrappedProcess

import java.nio.file.Path
import scala.sys.process.Process
import scala.util.{Failure, Try}

/** Represents information required to run a system command.
*
* @param command the command and its arguments that should be executed
* @param extraEnv environment variables that should be overridden
* @param workingDirectory the working directory in which the command should be executed (if None, the working directory is not overridden and is inherited instead)
*/
case class Command(command: Seq[String], extraEnv: Seq[(String, String)]) {
case class Command(
command: Seq[String],
extraEnv: Seq[(String, String)],
workingDirectory: Option[Path]
) {
private val logger = Logger[Command]

/** Runs the command and returns its exit code.
Expand Down Expand Up @@ -79,6 +85,7 @@ case class Command(command: Seq[String], extraEnv: Seq[(String, String)]) {
for ((key, value) <- extraEnv) {
processBuilder.environment().put(key, value)
}
workingDirectory.foreach(path => processBuilder.directory(path.toFile))
processBuilder
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package org.enso.runtimeversionmanager.runner

import org.enso.semver.SemVer

import java.nio.file.Path

/** Represents settings that are used to launch the runner JAR.
*
* @param engineVersion Enso engine version to use
* @param runnerArguments arguments that should be passed to the runner
* @param workingDirectory the working directory override
* @param connectLoggerIfAvailable specifies if the ran component should
* connect to launcher's logging service
*/
case class RunSettings(
engineVersion: SemVer,
runnerArguments: Seq[String],
workingDirectory: Option[Path],
connectLoggerIfAvailable: Boolean
)
Loading
Loading