Skip to content

Commit dfbd9f8

Browse files
authored
Add support for multi-file CLI command scripts embedded into the lute executable (#314)
## Summary In this PR, we add support for running command-line Luau scripts that are stored in the `cli/commands` directory. If we have `cli/commands/foo.luau` or `cli/commands/foo/init.luau`, running `lute foo` will automatically execute the contents of that script (with support for relative requires as well). ## Implementation details We create a new `CliVfs` that is optionally be embedded in `RequireVfs`. The CLI enables this, but `vm.create` does not, as it does not need a custom VFS for command-line scripts. The actual implementation of the VFS is nearly identical to `StdLibVfs`. The embedding of the CLI scripts is done using Luthier to generate source files for a new `Lute.CLI.Commands` target. This is similar to how the `@std` libraries were embedded into the `Lute.Std` target. - #138 - #205 While working on this, I noticed that `luthier.luau` was incomplete. Due to the lack of an `os.walk` API like we have in Python, `luthier.luau` doesn't correctly handle nested directories that need to be embedded. To solve this, I have added a `traverseFileTree` function that effectively accomplishes the same thing. Because of slight differences in the order files are processed by `os.walk` and `traverseFileTree`, `luthier.py` and `luthier.luau` spit out different hashes. I think this is fine. When only using one of the tools in isolation, the caching seems to work as expected.
1 parent 1a1625b commit dfbd9f8

File tree

15 files changed

+575
-23
lines changed

15 files changed

+575
-23
lines changed

CMakeLists.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ add_library(Lute.VM STATIC)
2020
add_library(Lute.Process STATIC)
2121
add_library(Lute.System STATIC)
2222
add_library(Lute.Time STATIC)
23+
add_library(Lute.CLI.Commands STATIC)
2324

2425
# luau setup
2526
set(LUAU_BUILD_CLI OFF)
@@ -105,6 +106,7 @@ target_compile_features(Lute.Process PUBLIC cxx_std_17)
105106
target_compile_features(Lute.System PUBLIC cxx_std_17)
106107
target_compile_features(Lute.Time PUBLIC cxx_std_17)
107108
target_compile_features(Lute.Test PUBLIC cxx_std_17)
109+
target_compile_features(Lute.CLI.Commands PUBLIC cxx_std_17)
108110

109111
target_include_directories(Lute.Runtime PUBLIC runtime/include ${LIBUV_INCLUDE_DIR})
110112
target_include_directories(Lute.Crypto PUBLIC crypto/include ${LIBUV_INCLUDE_DIR})
@@ -117,8 +119,9 @@ target_include_directories(Lute.VM PUBLIC vm/include ${LIBUV_INCLUDE_DIR})
117119
target_include_directories(Lute.Process PUBLIC process/include ${LIBUV_INCLUDE_DIR})
118120
target_include_directories(Lute.System PUBLIC system/include ${LIBUV_INCLUDE_DIR})
119121
target_include_directories(Lute.Time PUBLIC time/include ${LIBUV_INCLUDE_DIR})
122+
target_include_directories(Lute.CLI.Commands PUBLIC cli)
120123

121-
target_link_libraries(Lute.Runtime PUBLIC Luau.Require PRIVATE Luau.CLI.lib Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Lute.Std uv_a)
124+
target_link_libraries(Lute.Runtime PUBLIC Luau.Require PRIVATE Luau.CLI.lib Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Lute.Std Lute.CLI.Commands uv_a)
122125
target_link_libraries(Lute.Crypto PRIVATE Lute.Runtime Luau.VM uv_a ssl crypto)
123126
target_link_libraries(Lute.Fs PRIVATE Lute.Runtime Luau.VM uv_a)
124127
target_link_libraries(Lute.Luau PRIVATE Lute.Runtime Luau.VM uv_a Luau.Analysis Luau.Ast Luau.Compiler)
@@ -128,7 +131,7 @@ target_link_libraries(Lute.VM PRIVATE Lute.Runtime Luau.VM Luau.Require uv_a)
128131
target_link_libraries(Lute.Process PRIVATE Lute.Runtime Luau.VM uv_a)
129132
target_link_libraries(Lute.System PRIVATE Lute.Runtime Luau.VM uv_a)
130133
target_link_libraries(Lute.Time PRIVATE Lute.Runtime Luau.VM uv_a)
131-
target_link_libraries(Lute.CLI PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.Analysis Luau.VM Lute.Runtime Lute.Crypto Lute.Fs Lute.Luau Lute.Net Lute.Task Lute.VM Lute.Process Lute.System Lute.Time Luau.CLI.lib)
134+
target_link_libraries(Lute.CLI PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.Analysis Luau.VM Lute.CLI.Commands Lute.Runtime Lute.Crypto Lute.Fs Lute.Luau Lute.Net Lute.Task Lute.VM Lute.Process Lute.System Lute.Time Luau.CLI.lib)
132135
target_link_libraries(Lute.Test PRIVATE Lute.Runtime Luau.CLI.lib)
133136

134137
set(LUTE_OPTIONS)
@@ -158,5 +161,6 @@ target_compile_options(Lute.VM PRIVATE ${LUTE_OPTIONS})
158161
target_compile_options(Lute.Process PRIVATE ${LUTE_OPTIONS})
159162
target_compile_options(Lute.System PRIVATE ${LUTE_OPTIONS})
160163
target_compile_options(Lute.Time PRIVATE ${LUTE_OPTIONS})
164+
target_compile_options(Lute.CLI.Commands PRIVATE ${LUTE_OPTIONS})
161165
target_compile_options(Lute.CLI PRIVATE ${LUTE_OPTIONS})
162166
target_compile_options(Lute.Test PRIVATE ${LUTE_OPTIONS})

Sources.cmake

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
target_sources(Lute.Runtime PRIVATE
2+
runtime/include/lute/clivfs.h
23
runtime/include/lute/filevfs.h
34
runtime/include/lute/modulepath.h
45
runtime/include/lute/options.h
@@ -9,6 +10,7 @@ target_sources(Lute.Runtime PRIVATE
910
runtime/include/lute/stdlibvfs.h
1011
runtime/include/lute/userdatas.h
1112

13+
runtime/src/clivfs.cpp
1214
runtime/src/filevfs.cpp
1315
runtime/src/modulepath.cpp
1416
runtime/src/options.cpp
@@ -72,6 +74,14 @@ target_sources(Lute.Process PRIVATE
7274
process/src/process.cpp
7375
)
7476

77+
target_sources(Lute.CLI.Commands PRIVATE
78+
cli/generated/commands.h
79+
cli/generated/commands.cpp
80+
81+
cli/clicommands.h
82+
cli/clicommands.cpp
83+
)
84+
7585
target_sources(Lute.CLI PRIVATE
7686
cli/main.cpp
7787
cli/compile.h

cli/clicommands.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include "clicommands.h"
2+
3+
// This file provides luteclicommands and is auto-generated by luthier.luau.
4+
// If it is not present or outdated, re-configure using luthier.luau.
5+
#include "generated/commands.h"
6+
7+
#include <array>
8+
#include <string_view>
9+
#include <utility>
10+
11+
CliModuleResult getCliModule(std::string_view path)
12+
{
13+
for (const auto& [pathInLib, contents] : luteclicommands)
14+
{
15+
if (path != pathInLib)
16+
continue;
17+
18+
if (contents == "#directory")
19+
return {CliModuleType::Directory};
20+
else
21+
return {CliModuleType::Module, contents};
22+
}
23+
24+
return {CliModuleType::NotFound};
25+
}
26+
27+
std::optional<CliCommandResult> getCliCommand(std::string_view command)
28+
{
29+
const std::array<std::string, 2> pathOptions = {"@cli/" + std::string(command) + ".luau", "@cli/" + std::string(command) + "/init.luau"};
30+
31+
for (const std::string& path : pathOptions)
32+
{
33+
CliModuleResult result = getCliModule(path);
34+
if (result.type == CliModuleType::Module)
35+
{
36+
return CliCommandResult{result.contents, path};
37+
}
38+
}
39+
40+
return std::nullopt;
41+
}

cli/clicommands.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
3+
#include <optional>
4+
#include <string>
5+
#include <string_view>
6+
7+
enum class CliModuleType
8+
{
9+
Module,
10+
Directory,
11+
NotFound,
12+
};
13+
14+
struct CliModuleResult
15+
{
16+
CliModuleType type;
17+
std::string_view contents;
18+
};
19+
20+
CliModuleResult getCliModule(std::string_view path);
21+
22+
struct CliCommandResult
23+
{
24+
std::string_view contents;
25+
std::string path;
26+
};
27+
28+
std::optional<CliCommandResult> getCliCommand(std::string_view command);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
return { "Hello, world!" }

cli/commands/helloworld/init.luau

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
local result = require("@self/helloworld")
2+
print(result[1])

cli/main.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
#include "Luau/Parser.h"
66
#include "Luau/Require.h"
77

8+
#include "clicommands.h"
89
#include "lua.h"
910
#include "lualib.h"
11+
#include "lute/clivfs.h"
1012
#include "uv.h"
1113

1214
#include "lute/crypto.h"
@@ -25,6 +27,7 @@
2527

2628
#include "compile.h"
2729
#include "tc.h"
30+
#include <iostream>
2831

2932
#ifdef _WIN32
3033
#include <Windows.h>
@@ -50,7 +53,7 @@ static void* createCliRequireContext(lua_State* L)
5053
if (!ctx)
5154
luaL_error(L, "unable to allocate RequireCtx");
5255

53-
ctx = new (ctx) RequireCtx{};
56+
ctx = new (ctx) RequireCtx{CliVfs{}};
5457

5558
// Store RequireCtx in the registry to keep it alive for the lifetime of
5659
// this lua_State. Memory address is used as a key to avoid collisions.
@@ -391,6 +394,15 @@ int handleCompileCommand(int argc, char** argv, int argOffset)
391394
return compileScript(inputFilePath, outputFilePath, argv[0]);
392395
}
393396

397+
int handleCliCommand(CliCommandResult result)
398+
{
399+
Runtime runtime;
400+
lua_State* L = setupState(runtime);
401+
402+
std::string bytecode = Luau::compile(std::string(result.contents), copts());
403+
return runBytecode(runtime, bytecode, "@" + result.path, L) ? 0 : 1;
404+
}
405+
394406
int main(int argc, char** argv)
395407
{
396408
Luau::assertHandler() = assertionHandler;
@@ -440,6 +452,10 @@ int main(int argc, char** argv)
440452
displayHelp(argv[0]);
441453
return 0;
442454
}
455+
else if (std::optional<CliCommandResult> result = getCliCommand(command); result)
456+
{
457+
return handleCliCommand(*result);
458+
}
443459
else
444460
{
445461
// Default to 'run' command

runtime/include/lute/clivfs.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
#include "lute/modulepath.h"
4+
5+
#include <optional>
6+
7+
class CliVfs
8+
{
9+
public:
10+
NavigationStatus resetToPath(const std::string& path);
11+
12+
NavigationStatus toParent();
13+
NavigationStatus toChild(const std::string& name);
14+
15+
bool isModulePresent() const;
16+
std::string getIdentifier() const;
17+
std::optional<std::string> getContents(const std::string& path) const;
18+
19+
bool isConfigPresent() const;
20+
std::optional<std::string> getConfig() const;
21+
22+
private:
23+
std::optional<ModulePath> modulePath;
24+
};

runtime/include/lute/require.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include "lute/clivfs.h"
34
#include "lute/requirevfs.h"
45

56
#include "Luau/Require.h"
@@ -10,5 +11,8 @@ void requireConfigInit(luarequire_Configuration* config);
1011

1112
struct RequireCtx
1213
{
14+
RequireCtx();
15+
RequireCtx(CliVfs cliVfs);
16+
1317
RequireVfs vfs;
1418
};

runtime/include/lute/requirevfs.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
#pragma once
22

3+
#include "lute/clivfs.h"
34
#include "lute/filevfs.h"
45
#include "lute/modulepath.h"
56
#include "lute/stdlibvfs.h"
67

78
#include "lua.h"
89

10+
#include <optional>
911
#include <string>
1012

1113
class RequireVfs
1214
{
1315
public:
16+
RequireVfs() = default;
17+
RequireVfs(CliVfs cliVfs);
18+
1419
bool isRequireAllowed(lua_State* L, std::string_view requirerChunkname) const;
1520

1621
NavigationStatus reset(lua_State* L, std::string_view requirerChunkname);
@@ -34,13 +39,15 @@ class RequireVfs
3439
{
3540
Disk,
3641
Std,
42+
Cli,
3743
Lute,
3844
};
3945

4046
VFSType vfsType = VFSType::Disk;
4147

4248
FileVfs fileVfs;
4349
StdLibVfs stdLibVfs;
50+
std::optional<CliVfs> cliVfs = std::nullopt;
4451
std::string lutePath;
4552

4653
bool atFakeRoot = false;

0 commit comments

Comments
 (0)