koffi 0.9.33 → 0.9.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +2 -1
- package/README.md +81 -108
- package/package.json +5 -4
- package/src/call_arm32.cc +24 -10
- package/src/call_arm64.cc +6 -2
- package/src/call_x64_sysv.cc +6 -2
- package/src/call_x64_win.cc +6 -2
- package/src/call_x86.cc +7 -3
- package/src/ffi.cc +78 -61
- package/src/ffi.hh +2 -2
- package/src/parser.cc +246 -0
- package/src/parser.hh +63 -0
- package/src/util.cc +23 -0
- package/src/util.hh +3 -0
- package/test/registry/machines.json +70 -0
- package/test/test.js +115 -7
- package/test/tests/misc.js +16 -16
- package/test/tests/raylib.js +8 -8
- package/test/tests/sqlite.js +9 -9
package/src/ffi.hh
CHANGED
|
@@ -100,12 +100,12 @@ struct LibraryHolder {
|
|
|
100
100
|
};
|
|
101
101
|
|
|
102
102
|
enum class CallConvention {
|
|
103
|
-
|
|
103
|
+
Cdecl,
|
|
104
104
|
Stdcall,
|
|
105
105
|
Fastcall
|
|
106
106
|
};
|
|
107
107
|
static const char *const CallConventionNames[] = {
|
|
108
|
-
"
|
|
108
|
+
"Cdecl",
|
|
109
109
|
"Stdcall",
|
|
110
110
|
"Fastcall"
|
|
111
111
|
};
|
package/src/parser.cc
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// This program is free software: you can redistribute it and/or modify
|
|
2
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
3
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
4
|
+
// (at your option) any later version.
|
|
5
|
+
//
|
|
6
|
+
// This program is distributed in the hope that it will be useful,
|
|
7
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
8
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
9
|
+
// GNU Affero General Public License for more details.
|
|
10
|
+
//
|
|
11
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
12
|
+
// along with this program. If not, see https://www.gnu.org/licenses/.
|
|
13
|
+
|
|
14
|
+
#include "vendor/libcc/libcc.hh"
|
|
15
|
+
#include "ffi.hh"
|
|
16
|
+
#include "parser.hh"
|
|
17
|
+
|
|
18
|
+
#include <napi.h>
|
|
19
|
+
|
|
20
|
+
namespace RG {
|
|
21
|
+
|
|
22
|
+
bool PrototypeParser::Parse(Napi::String proto, FunctionInfo *func)
|
|
23
|
+
{
|
|
24
|
+
if (!proto.IsString()) {
|
|
25
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for prototype, expected string", GetValueType(instance, proto));
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
tokens.Clear();
|
|
30
|
+
offset = 0;
|
|
31
|
+
valid = true;
|
|
32
|
+
|
|
33
|
+
Tokenize(std::string(proto).c_str());
|
|
34
|
+
|
|
35
|
+
func->ret.type = ParseType();
|
|
36
|
+
if (Match("__cdecl")) {
|
|
37
|
+
func->convention = CallConvention::Cdecl;
|
|
38
|
+
} else if (Match("__stdcall")) {
|
|
39
|
+
func->convention = CallConvention::Stdcall;
|
|
40
|
+
} else if (Match("__fastcall")) {
|
|
41
|
+
func->convention = CallConvention::Fastcall;
|
|
42
|
+
}
|
|
43
|
+
func->name = ParseIdentifier();
|
|
44
|
+
|
|
45
|
+
Consume("(");
|
|
46
|
+
if (offset < tokens.len && tokens[offset] != ")") {
|
|
47
|
+
for (;;) {
|
|
48
|
+
ParameterInfo param = {};
|
|
49
|
+
|
|
50
|
+
if (Match("...")) {
|
|
51
|
+
func->variadic = true;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Match("_In_")) {
|
|
56
|
+
param.directions = 1;
|
|
57
|
+
} else if (Match("_Out_")) {
|
|
58
|
+
param.directions = 2;
|
|
59
|
+
} else if (Match("_Inout_")) {
|
|
60
|
+
param.directions = 3;
|
|
61
|
+
} else {
|
|
62
|
+
param.directions = 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
param.type = ParseType();
|
|
66
|
+
if (param.type->primitive == PrimitiveKind::Void) {
|
|
67
|
+
MarkError("Type void cannot be used as a parameter");
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if ((param.directions & 2) && (param.type->primitive != PrimitiveKind::Pointer ||
|
|
72
|
+
param.type->ref->primitive != PrimitiveKind::Record)) {
|
|
73
|
+
MarkError("Only object pointers can be used as out parameters (for now)");
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
offset += (offset < tokens.len && IsIdentifier(tokens[offset]));
|
|
78
|
+
|
|
79
|
+
if (func->parameters.len >= MaxParameters) {
|
|
80
|
+
MarkError("Functions cannot have more than %1 parameters", MaxParameters);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if ((param.directions & 2) && ++func->out_parameters >= MaxOutParameters) {
|
|
84
|
+
MarkError("Functions cannot have more than out %1 parameters", MaxOutParameters);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
param.offset = func->parameters.len;
|
|
89
|
+
|
|
90
|
+
func->parameters.Append(param);
|
|
91
|
+
|
|
92
|
+
if (offset >= tokens.len || tokens[offset] != ",")
|
|
93
|
+
break;
|
|
94
|
+
offset++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
Consume(")");
|
|
98
|
+
|
|
99
|
+
Match(";");
|
|
100
|
+
if (offset < tokens.len) {
|
|
101
|
+
MarkError("Unexpected token '%1' after prototype", tokens[offset]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return valid;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
void PrototypeParser::Tokenize(const char *str)
|
|
108
|
+
{
|
|
109
|
+
for (Size i = 0; str[i]; i++) {
|
|
110
|
+
char c = str[i];
|
|
111
|
+
|
|
112
|
+
if (IsAsciiWhite(c)) {
|
|
113
|
+
continue;
|
|
114
|
+
} else if (IsAsciiAlpha(c) || c == '_') {
|
|
115
|
+
Size j = i;
|
|
116
|
+
while (str[++j] && (IsAsciiAlphaOrDigit(str[j]) || str[j] == '_'));
|
|
117
|
+
|
|
118
|
+
Span<const char> tok = MakeSpan(str + i, j - i);
|
|
119
|
+
tokens.Append(tok);
|
|
120
|
+
|
|
121
|
+
i = j - 1;
|
|
122
|
+
} else if (c == '.' && str[i + 1] == '.' && str[i + 2] == '.') {
|
|
123
|
+
tokens.Append("...");
|
|
124
|
+
i += 2;
|
|
125
|
+
} else {
|
|
126
|
+
Span<const char> tok = MakeSpan(str + i, 1);
|
|
127
|
+
tokens.Append(tok);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const TypeInfo *PrototypeParser::ParseType()
|
|
133
|
+
{
|
|
134
|
+
HeapArray<char> buf(&instance->str_alloc);
|
|
135
|
+
|
|
136
|
+
Size indirect = 0;
|
|
137
|
+
|
|
138
|
+
Size start = offset;
|
|
139
|
+
while (offset < tokens.len && IsIdentifier(tokens[offset])) {
|
|
140
|
+
Span<const char> tok = tokens[offset++];
|
|
141
|
+
|
|
142
|
+
if (tok != "const") {
|
|
143
|
+
buf.Append(tok);
|
|
144
|
+
buf.Append(' ');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (offset == start) {
|
|
148
|
+
if (offset < tokens.len) {
|
|
149
|
+
MarkError("Unexpected token '%1', expected identifier", tokens[offset]);
|
|
150
|
+
} else {
|
|
151
|
+
MarkError("Unexpected end of prototype, expected identifier");
|
|
152
|
+
}
|
|
153
|
+
return instance->types_map.FindValue("void", nullptr);
|
|
154
|
+
}
|
|
155
|
+
while (offset < tokens.len && tokens[offset] == "*") {
|
|
156
|
+
offset++;
|
|
157
|
+
indirect++;
|
|
158
|
+
}
|
|
159
|
+
buf.ptr[--buf.len] = 0;
|
|
160
|
+
|
|
161
|
+
if (TestStr(buf, "char") && indirect) {
|
|
162
|
+
buf.RemoveFrom(0);
|
|
163
|
+
Fmt(&buf, "string");
|
|
164
|
+
|
|
165
|
+
indirect--;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
while (buf.len) {
|
|
169
|
+
const TypeInfo *type = instance->types_map.FindValue(buf.ptr, nullptr);
|
|
170
|
+
|
|
171
|
+
if (type) {
|
|
172
|
+
for (Size i = 0; i < indirect; i++) {
|
|
173
|
+
type = GetPointerType(instance, type);
|
|
174
|
+
RG_ASSERT(type);
|
|
175
|
+
}
|
|
176
|
+
return type;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Truncate last token
|
|
180
|
+
{
|
|
181
|
+
Span<const char> remain;
|
|
182
|
+
SplitStrReverse(buf, ' ', &remain);
|
|
183
|
+
buf.len = remain.len;
|
|
184
|
+
buf.ptr[buf.len] = 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (indirect) {
|
|
188
|
+
offset -= indirect;
|
|
189
|
+
indirect = 0;
|
|
190
|
+
}
|
|
191
|
+
offset--;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
MarkError("Unknown type '%1'", tokens[start]);
|
|
195
|
+
return instance->types_map.FindValue("void", nullptr);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const char *PrototypeParser::ParseIdentifier()
|
|
199
|
+
{
|
|
200
|
+
if (offset >= tokens.len) {
|
|
201
|
+
MarkError("Unexpected end of prototype, expected identifier");
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
if (!IsIdentifier(tokens[offset])) {
|
|
205
|
+
MarkError("Unexpected token '%1', expected identifier", tokens[offset]);
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Span<const char> tok = tokens[offset++];
|
|
210
|
+
const char *ident = DuplicateString(tok, &instance->str_alloc).ptr;
|
|
211
|
+
|
|
212
|
+
return ident;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
bool PrototypeParser::Consume(const char *expect)
|
|
216
|
+
{
|
|
217
|
+
if (offset >= tokens.len) {
|
|
218
|
+
MarkError("Unexpected end of prototype, expected '%1'", expect);
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
if (tokens[offset] != expect) {
|
|
222
|
+
MarkError("Unexpected token '%1', expected '%2'", tokens[offset], expect);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
offset++;
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
bool PrototypeParser::Match(const char *expect)
|
|
231
|
+
{
|
|
232
|
+
if (offset < tokens.len && tokens[offset] == expect) {
|
|
233
|
+
offset++;
|
|
234
|
+
return true;
|
|
235
|
+
} else {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
bool PrototypeParser::IsIdentifier(Span<const char> tok) const
|
|
241
|
+
{
|
|
242
|
+
RG_ASSERT(tok.len);
|
|
243
|
+
return IsAsciiAlpha(tok[0]) || tok[0] == '_';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
}
|
package/src/parser.hh
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// This program is free software: you can redistribute it and/or modify
|
|
2
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
3
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
4
|
+
// (at your option) any later version.
|
|
5
|
+
//
|
|
6
|
+
// This program is distributed in the hope that it will be useful,
|
|
7
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
8
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
9
|
+
// GNU Affero General Public License for more details.
|
|
10
|
+
//
|
|
11
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
12
|
+
// along with this program. If not, see https://www.gnu.org/licenses/.
|
|
13
|
+
|
|
14
|
+
#pragma once
|
|
15
|
+
|
|
16
|
+
#include "vendor/libcc/libcc.hh"
|
|
17
|
+
#include "util.hh"
|
|
18
|
+
|
|
19
|
+
#include <napi.h>
|
|
20
|
+
|
|
21
|
+
namespace RG {
|
|
22
|
+
|
|
23
|
+
struct InstanceData;
|
|
24
|
+
struct TypeInfo;
|
|
25
|
+
struct FunctionInfo;
|
|
26
|
+
|
|
27
|
+
class PrototypeParser {
|
|
28
|
+
Napi::Env env;
|
|
29
|
+
InstanceData *instance;
|
|
30
|
+
|
|
31
|
+
// All these members are relevant to the current parse only, and get resetted each time
|
|
32
|
+
HeapArray<Span<const char>> tokens;
|
|
33
|
+
Size offset;
|
|
34
|
+
bool valid;
|
|
35
|
+
|
|
36
|
+
public:
|
|
37
|
+
PrototypeParser(Napi::Env env) : env(env), instance(env.GetInstanceData<InstanceData>()) {}
|
|
38
|
+
|
|
39
|
+
bool Parse(Napi::String proto, FunctionInfo *func);
|
|
40
|
+
|
|
41
|
+
private:
|
|
42
|
+
void Tokenize(const char *str);
|
|
43
|
+
|
|
44
|
+
const TypeInfo *ParseType();
|
|
45
|
+
const char *ParseIdentifier();
|
|
46
|
+
|
|
47
|
+
bool Consume(const char *expect);
|
|
48
|
+
bool Match(const char *expect);
|
|
49
|
+
|
|
50
|
+
bool IsIdentifier(Span<const char> tok) const;
|
|
51
|
+
|
|
52
|
+
template <typename... Args>
|
|
53
|
+
void MarkError(const char *fmt, Args... args)
|
|
54
|
+
{
|
|
55
|
+
if (valid) {
|
|
56
|
+
ThrowError<Napi::Error>(env, fmt, args...);
|
|
57
|
+
valid = false;
|
|
58
|
+
}
|
|
59
|
+
valid = false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
}
|
package/src/util.cc
CHANGED
|
@@ -54,6 +54,29 @@ const TypeInfo *ResolveType(const InstanceData *instance, Napi::Value value, int
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
const TypeInfo *GetPointerType(InstanceData *instance, const TypeInfo *ref)
|
|
58
|
+
{
|
|
59
|
+
char name_buf[256];
|
|
60
|
+
Fmt(name_buf, "%1%2*", ref->name, ref->primitive == PrimitiveKind::Pointer ? "" : " ");
|
|
61
|
+
|
|
62
|
+
TypeInfo *type = instance->types_map.FindValue(name_buf, nullptr);
|
|
63
|
+
|
|
64
|
+
if (!type) {
|
|
65
|
+
type = instance->types.AppendDefault();
|
|
66
|
+
|
|
67
|
+
type->name = DuplicateString(name_buf, &instance->str_alloc).ptr;
|
|
68
|
+
|
|
69
|
+
type->primitive = PrimitiveKind::Pointer;
|
|
70
|
+
type->size = RG_SIZE(void *);
|
|
71
|
+
type->align = RG_SIZE(void *);
|
|
72
|
+
type->ref = ref;
|
|
73
|
+
|
|
74
|
+
instance->types_map.Set(type);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return type;
|
|
78
|
+
}
|
|
79
|
+
|
|
57
80
|
const char *GetValueType(const InstanceData *instance, Napi::Value value)
|
|
58
81
|
{
|
|
59
82
|
for (const TypeInfo &type: instance->types) {
|
package/src/util.hh
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
namespace RG {
|
|
21
21
|
|
|
22
22
|
struct InstanceData;
|
|
23
|
+
struct TypeInfo;
|
|
24
|
+
struct FunctionInfo;
|
|
23
25
|
|
|
24
26
|
template <typename T, typename... Args>
|
|
25
27
|
void ThrowError(Napi::Env env, const char *msg, Args... args)
|
|
@@ -52,6 +54,7 @@ static inline T *AlignDown(T *ptr, Size align)
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
const TypeInfo *ResolveType(const InstanceData *instance, Napi::Value value, int *out_directions = nullptr);
|
|
57
|
+
const TypeInfo *GetPointerType(InstanceData *instance, const TypeInfo *type);
|
|
55
58
|
|
|
56
59
|
// Can be slow, only use for error messages
|
|
57
60
|
const char *GetValueType(const InstanceData *instance, Napi::Value value);
|
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
"shutdown": "sudo poweroff"
|
|
22
22
|
},
|
|
23
23
|
|
|
24
|
+
"builds": {
|
|
25
|
+
"Linux ARM32": {
|
|
26
|
+
"directory": "/home/debian/luigi",
|
|
27
|
+
"build": "node ../cnoke/cnoke.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
24
31
|
"tests": {
|
|
25
32
|
"Linux ARM32": {
|
|
26
33
|
"directory": "/home/debian/luigi",
|
|
@@ -58,6 +65,13 @@
|
|
|
58
65
|
"shutdown": "sudo poweroff"
|
|
59
66
|
},
|
|
60
67
|
|
|
68
|
+
"builds": {
|
|
69
|
+
"Linux ARM64": {
|
|
70
|
+
"directory": "/home/debian/luigi",
|
|
71
|
+
"build": "node ../cnoke/cnoke.js"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
61
75
|
"tests": {
|
|
62
76
|
"Linux ARM64": {
|
|
63
77
|
"directory": "/home/debian/luigi",
|
|
@@ -95,6 +109,13 @@
|
|
|
95
109
|
"shutdown": "sudo poweroff"
|
|
96
110
|
},
|
|
97
111
|
|
|
112
|
+
"builds": {
|
|
113
|
+
"Linux i386": {
|
|
114
|
+
"directory": "/home/debian/luigi",
|
|
115
|
+
"build": "node ../cnoke/cnoke.js"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
98
119
|
"tests": {
|
|
99
120
|
"Linux i386": {
|
|
100
121
|
"directory": "/home/debian/luigi",
|
|
@@ -132,6 +153,13 @@
|
|
|
132
153
|
"shutdown": "sudo poweroff"
|
|
133
154
|
},
|
|
134
155
|
|
|
156
|
+
"builds": {
|
|
157
|
+
"Linux x64": {
|
|
158
|
+
"directory": "/home/debian/luigi",
|
|
159
|
+
"build": "node ../cnoke/cnoke.js"
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
135
163
|
"tests": {
|
|
136
164
|
"Linux x64": {
|
|
137
165
|
"directory": "/home/debian/luigi",
|
|
@@ -169,6 +197,20 @@
|
|
|
169
197
|
"shutdown": "shutdown -s -t 0"
|
|
170
198
|
},
|
|
171
199
|
|
|
200
|
+
"builds": {
|
|
201
|
+
"Windows i386": {
|
|
202
|
+
"arch": "ia32",
|
|
203
|
+
"directory": "C:/Users/windows/Desktop/luigi32",
|
|
204
|
+
"build": "C:\\Node32\\node32.cmd node ../cnoke/cnoke.js"
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
"Windows x64": {
|
|
208
|
+
"arch": "x64",
|
|
209
|
+
"directory": "C:/Users/windows/Desktop/luigi64",
|
|
210
|
+
"build": "C:\\Node64\\node64.cmd node ../cnoke/cnoke.js"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
172
214
|
"tests": {
|
|
173
215
|
"Windows i386": {
|
|
174
216
|
"directory": "C:/Users/windows/Desktop/luigi32",
|
|
@@ -218,6 +260,13 @@
|
|
|
218
260
|
"shutdown": "sudo poweroff"
|
|
219
261
|
},
|
|
220
262
|
|
|
263
|
+
"builds": {
|
|
264
|
+
"FreeBSD x64": {
|
|
265
|
+
"directory": "/home/freebsd/luigi",
|
|
266
|
+
"build": "node ../cnoke/cnoke.js"
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
|
|
221
270
|
"tests": {
|
|
222
271
|
"FreeBSD x64": {
|
|
223
272
|
"directory": "/home/freebsd/luigi",
|
|
@@ -255,6 +304,13 @@
|
|
|
255
304
|
"shutdown": "sudo poweroff"
|
|
256
305
|
},
|
|
257
306
|
|
|
307
|
+
"builds": {
|
|
308
|
+
"FreeBSD i386": {
|
|
309
|
+
"directory": "/home/freebsd/luigi",
|
|
310
|
+
"build": "node ../cnoke/cnoke.js"
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
|
|
258
314
|
"tests": {
|
|
259
315
|
"FreeBSD i386": {
|
|
260
316
|
"directory": "/home/freebsd/luigi",
|
|
@@ -292,6 +348,13 @@
|
|
|
292
348
|
"shutdown": "sudo poweroff"
|
|
293
349
|
},
|
|
294
350
|
|
|
351
|
+
"builds": {
|
|
352
|
+
"FreeBSD ARM64": {
|
|
353
|
+
"directory": "/home/freebsd/luigi",
|
|
354
|
+
"build": "node ../cnoke/cnoke.js"
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
|
|
295
358
|
"tests": {
|
|
296
359
|
"FreeBSD ARM64": {
|
|
297
360
|
"directory": "/home/freebsd/luigi",
|
|
@@ -329,6 +392,13 @@
|
|
|
329
392
|
"shutdown": "sudo shutdown -h now"
|
|
330
393
|
},
|
|
331
394
|
|
|
395
|
+
"builds": {
|
|
396
|
+
"macOS x64": {
|
|
397
|
+
"directory": "/Users/macos/luigi",
|
|
398
|
+
"build": "PATH=/usr/local/bin:/usr/bin:/bin SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX11.3.sdk node ../cnoke/cnoke.js"
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
|
|
332
402
|
"tests": {
|
|
333
403
|
"macOS x64": {
|
|
334
404
|
"directory": "/Users/macos/luigi",
|
package/test/test.js
CHANGED
|
@@ -24,6 +24,7 @@ const { spawn, spawnSync } = require('child_process');
|
|
|
24
24
|
const { NodeSSH } = require('node-ssh');
|
|
25
25
|
const chalk = require('chalk');
|
|
26
26
|
const minimatch = require('minimatch');
|
|
27
|
+
const tar = require('tar');
|
|
27
28
|
|
|
28
29
|
// Globals
|
|
29
30
|
|
|
@@ -56,6 +57,7 @@ async function main() {
|
|
|
56
57
|
|
|
57
58
|
if (process.argv.length >= 3 && process.argv[2][0] != '-') {
|
|
58
59
|
switch (process.argv[2]) {
|
|
60
|
+
case 'build': { command = build; i++ } break;
|
|
59
61
|
case 'test': { command = test; i++; } break;
|
|
60
62
|
case 'start': { command = start; i++; } break;
|
|
61
63
|
case 'stop': { command = stop; i++; } break;
|
|
@@ -261,10 +263,109 @@ async function start(detach = true) {
|
|
|
261
263
|
return success;
|
|
262
264
|
}
|
|
263
265
|
|
|
264
|
-
async function
|
|
266
|
+
async function build() {
|
|
267
|
+
let success = true;
|
|
268
|
+
|
|
269
|
+
success &= await start(false);
|
|
270
|
+
success &= await copy(machine => Object.values(machine.builds).map(build => build.directory));
|
|
271
|
+
|
|
272
|
+
console.log('>> Run build commands...');
|
|
273
|
+
await Promise.all(machines.map(async machine => {
|
|
274
|
+
if (ignore.has(machine))
|
|
275
|
+
return;
|
|
276
|
+
|
|
277
|
+
await Promise.all(Object.keys(machine.builds).map(async suite => {
|
|
278
|
+
let build = machine.builds[suite];
|
|
279
|
+
|
|
280
|
+
let cmd = build.build;
|
|
281
|
+
let cwd = build.directory + '/koffi';
|
|
282
|
+
|
|
283
|
+
let start = process.hrtime.bigint();
|
|
284
|
+
let ret = await exec_remote(machine, cmd, cwd);
|
|
285
|
+
let time = Number((process.hrtime.bigint() - start) / 1000000n);
|
|
286
|
+
|
|
287
|
+
if (ret.code == 0) {
|
|
288
|
+
log(machine, `${suite} > Build`, chalk.bold.green(`[${(time / 1000).toFixed(2)}s]`));
|
|
289
|
+
} else {
|
|
290
|
+
log(machine, `${suite} > Build`, chalk.bold.red('[error]'));
|
|
291
|
+
|
|
292
|
+
if (ret.stdout || ret.stderr)
|
|
293
|
+
console.error('');
|
|
294
|
+
|
|
295
|
+
let align = log.align + 9;
|
|
296
|
+
if (ret.stdout) {
|
|
297
|
+
let str = ' '.repeat(align) + 'Standard output:\n' +
|
|
298
|
+
chalk.yellow(ret.stdout.replace(/^/gm, ' '.repeat(align + 4))) + '\n';
|
|
299
|
+
console.error(str);
|
|
300
|
+
}
|
|
301
|
+
if (ret.stderr) {
|
|
302
|
+
let str = ' '.repeat(align) + 'Standard error:\n' +
|
|
303
|
+
chalk.yellow(ret.stderr.replace(/^/gm, ' '.repeat(align + 4))) + '\n';
|
|
304
|
+
console.error(str);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
success = false;
|
|
308
|
+
}
|
|
309
|
+
}));
|
|
310
|
+
}));
|
|
311
|
+
|
|
312
|
+
console.log('>> Get build artifacts');
|
|
313
|
+
{
|
|
314
|
+
let json = fs.readFileSync(root_dir + '/koffi/package.json', { encoding: 'utf-8' });
|
|
315
|
+
let version = JSON.parse(json).version;
|
|
316
|
+
|
|
317
|
+
await Promise.all(machines.map(async machine => {
|
|
318
|
+
if (ignore.has(machine))
|
|
319
|
+
return;
|
|
320
|
+
|
|
321
|
+
let copied = true;
|
|
322
|
+
|
|
323
|
+
await Promise.all(Object.keys(machine.builds).map(async suite => {
|
|
324
|
+
let build = machine.builds[suite];
|
|
325
|
+
|
|
326
|
+
let platform = build.platform || machine.info.platform;
|
|
327
|
+
let arch = build.arch || machine.info.arch;
|
|
328
|
+
|
|
329
|
+
let src_dir = build.directory + '/koffi/build';
|
|
330
|
+
let dest_dir = root_dir + `/koffi/build/qemu/${version}/koffi_${platform}_${arch}`;
|
|
331
|
+
|
|
332
|
+
fs.mkdirSync(dest_dir + '/build', { mode: 0o755, recursive: true });
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
await machine.ssh.getDirectory(dest_dir + '/build', src_dir, {
|
|
336
|
+
recursive: false,
|
|
337
|
+
concurrency: 4
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
tar.c({
|
|
341
|
+
gzip: true,
|
|
342
|
+
file: dest_dir + '.tar.gz',
|
|
343
|
+
sync: true,
|
|
344
|
+
cwd: dest_dir + '/..'
|
|
345
|
+
}, [path.basename(dest_dir)]);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.log(err);
|
|
348
|
+
ignore.add(machine);
|
|
349
|
+
success = false;
|
|
350
|
+
copied = false;
|
|
351
|
+
}
|
|
352
|
+
}));
|
|
353
|
+
|
|
354
|
+
let status = copied ? chalk.bold.green('[ok]') : chalk.bold.red('[error]');
|
|
355
|
+
log(machine, 'Download', status);
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (machines.some(machine => machine.started))
|
|
360
|
+
success &= await stop(false);
|
|
361
|
+
|
|
362
|
+
return success;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function copy(func) {
|
|
265
366
|
let success = true;
|
|
266
367
|
|
|
267
|
-
let snapshot_dir = fs.mkdtempSync(path.join(os.tmpdir(), '
|
|
368
|
+
let snapshot_dir = fs.mkdtempSync(path.join(os.tmpdir(), 'luigi_'));
|
|
268
369
|
process.on('exit', () => unlink_recursive(snapshot_dir));
|
|
269
370
|
|
|
270
371
|
console.log('>> Snapshot code...');
|
|
@@ -279,8 +380,6 @@ async function test() {
|
|
|
279
380
|
basename !== 'luiggi';
|
|
280
381
|
});
|
|
281
382
|
|
|
282
|
-
success &= await start(false);
|
|
283
|
-
|
|
284
383
|
console.log('>> Copy source code...');
|
|
285
384
|
await Promise.all(machines.map(async machine => {
|
|
286
385
|
if (ignore.has(machine))
|
|
@@ -288,15 +387,15 @@ async function test() {
|
|
|
288
387
|
|
|
289
388
|
let copied = true;
|
|
290
389
|
|
|
291
|
-
for (let
|
|
390
|
+
for (let directory of func(machine)) {
|
|
292
391
|
try {
|
|
293
|
-
await machine.ssh.exec('rm', ['-rf',
|
|
392
|
+
await machine.ssh.exec('rm', ['-rf', directory]);
|
|
294
393
|
} catch (err) {
|
|
295
394
|
// Fails often on Windows (busy directory or whatever), but rarely a problem
|
|
296
395
|
}
|
|
297
396
|
|
|
298
397
|
try {
|
|
299
|
-
await machine.ssh.putDirectory(snapshot_dir,
|
|
398
|
+
await machine.ssh.putDirectory(snapshot_dir, directory, {
|
|
300
399
|
recursive: true,
|
|
301
400
|
concurrency: 4
|
|
302
401
|
});
|
|
@@ -311,6 +410,15 @@ async function test() {
|
|
|
311
410
|
log(machine, 'Copy', status);
|
|
312
411
|
}));
|
|
313
412
|
|
|
413
|
+
return success;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function test() {
|
|
417
|
+
let success = true;
|
|
418
|
+
|
|
419
|
+
success &= await start(false);
|
|
420
|
+
success &= await copy(machine => Object.values(machine.tests).map(test => test.directory));
|
|
421
|
+
|
|
314
422
|
console.log('>> Run test commands...');
|
|
315
423
|
await Promise.all(machines.map(async machine => {
|
|
316
424
|
if (ignore.has(machine))
|