Skip to content

Commit

Permalink
Support projects with elm.json not at the root
Browse files Browse the repository at this point in the history
Expand the Elm Editor to support compiling modules for which we want to use an `elm.json` file that is not at the root of the project file tree:

+ Update methods in Elm Fullstack for more consistent APIs when modeling file paths: Use sequence of strings instead of depending on OS-specific separator character.
+ Expand the `ExecuteFileWithArguments` method in Elm Fullstack to support a working directory different from the root directory.
+ Expand the Elm Editor backend to forward a working directory for the `Elm make` command as given by the frontend.
+ Expand the Elm Editor frontend to select the matching `elm.json` file for the Elm make entry module chosen by the user.
  • Loading branch information
Viir committed Mar 15, 2021
1 parent 549190c commit 50feeb9
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,53 @@ public struct ProcessOutput
public int ExitCode;
}

static public (ProcessOutput processOutput, IReadOnlyCollection<(string name, IImmutableList<byte> content)> resultingFiles) ExecuteFileWithArguments(
IImmutableList<(string name, IImmutableList<byte> content)> environmentFiles,
static public (ProcessOutput processOutput, IReadOnlyCollection<(IImmutableList<string> path, IImmutableList<byte> content)> resultingFiles) ExecuteFileWithArguments(
IImmutableList<(IImmutableList<string> path, IImmutableList<byte> content)> environmentFiles,
byte[] executableFile,
string arguments,
IDictionary<string, string> environmentStrings)
IDictionary<string, string> environmentStrings,
IImmutableList<string> workingDirectory = null)
{
var workingDirectory = Filesystem.CreateRandomDirectoryInTempDirectory();
var containerDirectory = Filesystem.CreateRandomDirectoryInTempDirectory();

var executableFileName = "name-used-to-execute-file.exe";

var executableFilePathRelative = ImmutableList.Create(executableFileName);

foreach (var environmentFile in environmentFiles)
{
var environmentFilePath = Path.Combine(workingDirectory, environmentFile.name);
var environmentFilePath = Path.Combine(containerDirectory, Filesystem.MakePlatformSpecificPath(environmentFile.path));
var environmentFileDirectory = Path.GetDirectoryName(environmentFilePath);

Directory.CreateDirectory(environmentFileDirectory);

File.WriteAllBytes(environmentFilePath, environmentFile.content.ToArray());
}

var executableFilePath = Path.Combine(workingDirectory, executableFileName);
var executableFilePathAbsolute = Path.Combine(containerDirectory, executableFileName);

File.WriteAllBytes(executableFilePath, executableFile);
File.WriteAllBytes(executableFilePathAbsolute, executableFile);

if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var unixFileInfo = new Mono.Unix.UnixFileInfo(executableFilePath);
var unixFileInfo = new Mono.Unix.UnixFileInfo(executableFilePathAbsolute);

unixFileInfo.FileAccessPermissions |=
FileAccessPermissions.GroupExecute | FileAccessPermissions.UserExecute | FileAccessPermissions.OtherExecute |
FileAccessPermissions.GroupRead | FileAccessPermissions.UserRead | FileAccessPermissions.OtherRead;
}

var workingDirectoryAbsolute =
Path.Combine(
containerDirectory,
Filesystem.MakePlatformSpecificPath(workingDirectory ?? ImmutableList<string>.Empty));

var process = new System.Diagnostics.Process
{
StartInfo = new ProcessStartInfo
{
WorkingDirectory = workingDirectory,
FileName = executableFilePath,
WorkingDirectory = workingDirectoryAbsolute,
FileName = executableFilePathAbsolute,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
Expand All @@ -79,12 +87,12 @@ static public (ProcessOutput processOutput, IReadOnlyCollection<(string name, II

var createdFiles =
Filesystem.GetFilesFromDirectory(
directoryPath: workingDirectory,
filterByRelativeName: path => path != executableFileName);
directoryPath: containerDirectory,
filterByRelativeName: path => !path.SequenceEqual(executableFilePathRelative));

try
{
Directory.Delete(path: workingDirectory, recursive: true);
Directory.Delete(path: containerDirectory, recursive: true);
}
// Avoid crash in scenario like https://forum.botengine.org/t/farm-manager-tribal-wars-2-farmbot/3038/170
catch (UnauthorizedAccessException)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,18 @@ public class Filesystem
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "LOCALAPPDATA" : "HOME"),
"kalmit", ".cache");

static public IReadOnlyCollection<(string path, IImmutableList<byte> content)> GetAllFilesFromDirectory(string directoryPath) =>
static public IReadOnlyCollection<(IImmutableList<string> path, IImmutableList<byte> content)> GetAllFilesFromDirectory(string directoryPath) =>
GetFilesFromDirectory(
directoryPath: directoryPath,
filterByRelativeName: _ => true);

static public IReadOnlyCollection<(string path, IImmutableList<byte> content)> GetFilesFromDirectory(
static public IReadOnlyCollection<(IImmutableList<string> path, IImmutableList<byte> content)> GetFilesFromDirectory(
string directoryPath,
Func<string, bool> filterByRelativeName) =>
Func<IImmutableList<string>, bool> filterByRelativeName) =>
Directory.GetFiles(directoryPath, "*", SearchOption.AllDirectories)
.Select(filePath => (absolutePath: filePath, relativePath: GetRelativePath(directoryPath, filePath)))
.Select(filePath =>
(absolutePath: filePath,
relativePath: (IImmutableList<string>)GetRelativePath(directoryPath, filePath).Split(Path.DirectorySeparatorChar).ToImmutableList()))
.Where(filePath => filterByRelativeName(filePath.relativePath))
.Select(filePath => (filePath.relativePath, (IImmutableList<byte>)File.ReadAllBytes(filePath.absolutePath).ToImmutableList()))
.ToList();
Expand All @@ -45,5 +47,8 @@ static public string CreateRandomDirectoryInTempDirectory()
Directory.CreateDirectory(directory);
return directory;
}

static public string MakePlatformSpecificPath(IImmutableList<string> path) =>
string.Join(Path.DirectorySeparatorChar.ToString(), path);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@ static public Composition.TreeWithStringPath LoadSortedTreeFromPath(string path)
if (!Directory.Exists(path))
return null;

var blobs =
Filesystem.GetAllFilesFromDirectory(path)
.Select(file => (path: (System.Collections.Immutable.IImmutableList<string>)file.path.Split('/', '\\').ToImmutableList(), content: file.content))
.ToImmutableList();
var blobs = Filesystem.GetAllFilesFromDirectory(path);

return
Composition.SortedTreeFromSetOfBlobsWithStringPath(blobs);
return Composition.SortedTreeFromSetOfBlobsWithStringPath(blobs);
}
}
}
22 changes: 9 additions & 13 deletions implement/PersistentProcess/PersistentProcess.Common/Process.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,19 +197,17 @@ static public string CompileElm(
*/
var maxRetryCount = 2;

var command = "make " + MakePlatformSpecificPath(pathToFileWithElmEntryPoint) + " --output=\"" + outputFileName + "\" " + elmMakeCommandAppendix;
var command = "make " + Filesystem.MakePlatformSpecificPath(pathToFileWithElmEntryPoint) + " --output=\"" + outputFileName + "\" " + elmMakeCommandAppendix;

var attemptsResults = new List<(ExecutableFile.ProcessOutput processOutput, IReadOnlyCollection<(string name, IImmutableList<byte> content)> resultingFiles)>();
var attemptsResults = new List<(ExecutableFile.ProcessOutput processOutput, IReadOnlyCollection<(IImmutableList<string> path, IImmutableList<byte> content)> resultingFiles)>();

var platformSpecificFiles =
elmCodeFiles
.Select(elmCodeFile => (MakePlatformSpecificPath(elmCodeFile.Key), elmCodeFile.Value))
.ToImmutableList();
var environmentFiles =
elmCodeFiles.Select(file => (path: file.Key, content: file.Value)).ToImmutableList();

do
{
var commandResults = ExecutableFile.ExecuteFileWithArguments(
platformSpecificFiles,
environmentFiles,
GetElmExecutableFile,
command,
new Dictionary<string, string>()
Expand Down Expand Up @@ -243,13 +241,14 @@ An alternative would be retrying when this error is parsed from `commandResults.

attemptsResults.Add(commandResults);

var platformSpecificNewFiles =
var newFiles =
commandResults.resultingFiles
.Where(file => !platformSpecificFiles.Any(inputFile => inputFile.Item1 == file.name))
.Where(file => !environmentFiles.Any(inputFile => inputFile.Item1.SequenceEqual(file.path)))
.ToImmutableList();

var outputFileContent =
platformSpecificNewFiles.FirstOrDefault(resultFile => resultFile.name == outputFileName).content;
newFiles
.FirstOrDefault(resultFile => resultFile.path.SequenceEqual(ImmutableList.Create(outputFileName))).content;

if (outputFileContent != null)
return Encoding.UTF8.GetString(outputFileContent.ToArray());
Expand All @@ -271,9 +270,6 @@ An alternative would be retrying when this error is parsed from `commandResults.
"\nStandard Error:\n'" + lastAttemptResults.processOutput.StandardError + "'");
}

static public string MakePlatformSpecificPath(IImmutableList<string> path) =>
string.Join(Path.DirectorySeparatorChar.ToString(), path);

static public byte[] GetElmExecutableFile =>
CommonConversion.DecompressGzip(GetElmExecutableFileCompressedGzip);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ namespace Kalmit.PersistentProcess.WebHost
{
public class Program
{
static public string AppVersionId => "2021-03-13";
static public string AppVersionId => "2021-03-15";
}
}
4 changes: 2 additions & 2 deletions implement/elm-fullstack/elm-fullstack.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>elm_fullstack</RootNamespace>
<AssemblyName>elm-fs</AssemblyName>
<AssemblyVersion>2021.0313.0.0</AssemblyVersion>
<FileVersion>2021.0313.0.0</FileVersion>
<AssemblyVersion>2021.0315.0.0</AssemblyVersion>
<FileVersion>2021.0315.0.0</FileVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
41 changes: 20 additions & 21 deletions implement/example-apps/elm-editor/src/Backend/VolatileHost.elm
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ volatileHostScript =
// https://www.nuget.org/api/v2/package/Newtonsoft.Json/12.0.2
#r "sha256:b9b4e633ea6c728bad5f7cbbef7f8b842f7e10181731dbe5ec3cd995a6f60287"
// from elm-fullstack-separate-assemblies-52aa3ed298b05c93d37503d601de56211d2163be-linux-x64
#r "sha256:bf80ff2fb8a61b6b2a0d22c49771aad07fcc07a282bf7768730b3a525c84fd2d"
#r "sha256:ffbbf81111c3981a7f09dfb9be6e3d3255a9669a95d468245350a644928cf1a4"
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -91,7 +90,9 @@ public class ElmMakeRequestStructure
{
public IReadOnlyList<FileWithPath> files;
public IReadOnlyList<string> entryPointFilePath;
public IReadOnlyList<string> entryPointFilePathFromWorkingDirectory;
public IReadOnlyList<string> workingDirectoryPath;
}
public class FormatElmModuleTextResponseStructure
Expand Down Expand Up @@ -275,25 +276,24 @@ ElmMakeResponseStructure ElmMake(ElmMakeRequestStructure elmMakeRequest)
file => (IImmutableList<string>)file.path.ToImmutableList(),
file => (IImmutableList<byte>)Convert.FromBase64String(file.contentBase64).ToImmutableList());
var platformSpecificFiles =
elmCodeFiles
.Select(elmCodeFile => (MakePlatformSpecificPath(elmCodeFile.Key), elmCodeFile.Value))
.ToImmutableList();
var environmentFiles =
elmCodeFiles.Select(file => (path: file.Key, content: file.Value)).ToImmutableList();
var entryPointFilePath = MakePlatformSpecificPath(elmMakeRequest.entryPointFilePath);
var entryPointFilePathFromWorkingDirectory =
MakePlatformSpecificPath(elmMakeRequest.entryPointFilePathFromWorkingDirectory);
var elmMakeOutputFileName = "elm-make-output.html";
var commandLineCommonArguments = "make " + entryPointFilePath;
var commandLineCommonArguments = "make " + entryPointFilePathFromWorkingDirectory;
var commandLineArguments = commandLineCommonArguments + " --output=" + elmMakeOutputFileName;
var reportJsonCommandLineArguments = commandLineCommonArguments + " --report=json";
(Kalmit.ExecutableFile.ProcessOutput processOutput, IReadOnlyCollection<(string name, IImmutableList<byte> content)> resultingFiles) commandResultsFromArguments(string arguments)
(Kalmit.ExecutableFile.ProcessOutput processOutput, IReadOnlyCollection<(IImmutableList<string> path, IImmutableList<byte> content)> resultingFiles) commandResultsFromArguments(string arguments)
{
return
Kalmit.ExecutableFile.ExecuteFileWithArguments(
platformSpecificFiles,
environmentFiles,
GetElmExecutableFile,
arguments,
new Dictionary<string, string>()
Expand Down Expand Up @@ -323,21 +323,18 @@ ElmMakeResponseStructure ElmMake(ElmMakeRequestStructure elmMakeRequest)
An alternative would be retrying when this error is parsed from `commandResults.processOutput.StandardError`.
*/
{"ELM_HOME", GetElmHomeDirectory()},
});
},
workingDirectory: elmMakeRequest.workingDirectoryPath.ToImmutableList());
}
var commandResults = commandResultsFromArguments(commandLineArguments);
var platformSpecificNewFiles =
commandResults.resultingFiles
.Where(file => !platformSpecificFiles.Any(inputFile => inputFile.Item1 == file.name))
.ToImmutableList();
var newFiles =
platformSpecificNewFiles
commandResults.resultingFiles
.Where(file => !environmentFiles.Any(inputFile => inputFile.Item1.SequenceEqual(file.path)))
.Select(file => new FileWithPath
{
path = file.name.Split('/', '\\\\'),
path = file.path,
contentBase64 = Convert.ToBase64String(file.content.ToArray()),
})
.ToImmutableList();
Expand Down Expand Up @@ -418,17 +415,19 @@ static public class ElmFormat
{
var elmModuleFileName = "ElmModuleToFormat.elm";
var elmModuleFilePath = ImmutableList.Create(elmModuleFileName);
var elmFormatResult =
Kalmit.ExecutableFile.ExecuteFileWithArguments(
ImmutableList.Create(
(elmModuleFileName, (IImmutableList<byte>)System.Text.Encoding.UTF8.GetBytes(originalModuleText).ToImmutableList())),
((IImmutableList<string>)elmModuleFilePath, (IImmutableList<byte>)System.Text.Encoding.UTF8.GetBytes(originalModuleText).ToImmutableList())),
GetElmFormatExecutableFile,
" " + elmModuleFileName + " --yes",
environmentStrings: null);
var resultingFile =
elmFormatResult.resultingFiles
.FirstOrDefault(file => file.name.EndsWith(elmModuleFileName))
.FirstOrDefault(file => file.path.SequenceEqual(elmModuleFilePath))
.content;
var formattedText =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ type ResponseStructure

type alias ElmMakeRequestStructure =
{ files : List FileWithPath
, entryPointFilePath : List String
, workingDirectoryPath : List String
, entryPointFilePathFromWorkingDirectory : List String
}


Expand Down
35 changes: 28 additions & 7 deletions implement/example-apps/elm-editor/src/FrontendWeb/Main.elm
Original file line number Diff line number Diff line change
Expand Up @@ -868,19 +868,34 @@ elmMakeRequestForFileOpenedInEditor workspace =
Nothing ->
Nothing

Just filePath ->
Just entryPointFilePath ->
let
base64FromBytes : Bytes.Bytes -> String
base64FromBytes =
Base64.fromBytes
>> Maybe.withDefault "Error encoding in base64"

entryPointFilePath =
filePath
allFilePaths =
workspace.fileTree
|> ProjectState.flatListOfBlobsFromFileTreeNode
|> List.map Tuple.first

directoryContainsElmJson directoryPath =
allFilePaths |> List.member (directoryPath ++ [ "elm.json" ])

workingDirectoryPath =
entryPointFilePath
|> List.reverse
|> List.drop 1
|> List.reverse
|> List.Extra.inits
|> List.sortBy List.length
|> List.filter directoryContainsElmJson
|> List.head
|> Maybe.withDefault []
in
Just
{ entryPointFilePath = entryPointFilePath
, files =
{ files =
workspace.fileTree
|> ProjectState.flatListOfBlobsFromFileTreeNode
|> List.map
Expand All @@ -889,6 +904,8 @@ elmMakeRequestForFileOpenedInEditor workspace =
, contentBase64 = content |> base64FromBytes
}
)
, workingDirectoryPath = workingDirectoryPath
, entryPointFilePathFromWorkingDirectory = entryPointFilePath |> List.drop (List.length workingDirectoryPath)
}


Expand Down Expand Up @@ -1701,14 +1718,18 @@ viewOutputPaneContent state =
Just elmMakeRequest.files
== (elmMakeRequestFromCurrentState |> Maybe.map .files)

elmMakeRequestEntryPointFilePathAbs =
elmMakeRequest.workingDirectoryPath
++ elmMakeRequest.entryPointFilePathFromWorkingDirectory

warnAboutOutdatedCompilationText =
if
Just elmMakeRequest.entryPointFilePath
Just elmMakeRequestEntryPointFilePathAbs
/= filePathOpenedInEditorFromWorkspace state
then
Just
("⚠️ Last compilation started for another file: '"
++ String.join "/" elmMakeRequest.entryPointFilePath
++ String.join "/" elmMakeRequestEntryPointFilePathAbs
++ "'"
)

Expand Down
3 changes: 1 addition & 2 deletions implement/test-elm-fullstack/TestSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ static public IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>>
string directoryPath) =>
ElmApp.ToFlatDictionaryWithPathComparer(
Filesystem.GetAllFilesFromDirectory(directoryPath)
.OrderBy(file => file.path)
.Select(filePathAndContent => ((IImmutableList<string>)filePathAndContent.path.Split(new[] { '/', '\\' }).ToImmutableList(), filePathAndContent.content)));
.OrderBy(file => string.Join('/', file.path)));

static public IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> AsLoweredElmApp(
IImmutableDictionary<IImmutableList<string>, IImmutableList<byte>> originalAppFiles) =>
Expand Down

0 comments on commit 50feeb9

Please sign in to comment.