Removed Windows constant generation code as we will no longer need it.
This commit is contained in:
parent
6a5a28d917
commit
70e45bef57
|
@ -1,43 +0,0 @@
|
||||||
// 11 july 2014
|
|
||||||
|
|
||||||
package ui
|
|
||||||
|
|
||||||
// wfunc kernel32 GetModuleHandleW *uint16 uintptr
|
|
||||||
// wfunc kernel32 GetStartupInfoW *s_STARTUPINFOW void
|
|
||||||
// wfunc user32 LoadIconW uintptr uintptr uintptr
|
|
||||||
// wfunc user32 LoadCursorW uintptr uintptr uintptr
|
|
||||||
// wfunc user32 GetMessageW *s_MSG uintptr t_UINT t_UINT t_BOOL
|
|
||||||
|
|
||||||
// these two don't technically return void but let's pretend they do since their return values are irrelevant/not indicative of anything useful
|
|
||||||
// wfunc user32 TranslateMessage *s_MSG void
|
|
||||||
// wfunc user32 DispatchMessageW *s_MSG void
|
|
||||||
|
|
||||||
// wfunc user32 PostMessageW uintptr t_UINT t_WPARAM t_LPARAM uintptr
|
|
||||||
// wfunc user32 RegisterClassW *s_WNDCLASSW uintptr
|
|
||||||
|
|
||||||
// TODO narrow down argument types
|
|
||||||
// wfunc user32 CreateWindowExW uintptr *uint16 *uint16 uintptr uintptr uintptr uintptr uintptr uintptr uintptr uintptr unsafe.Pointer uintptr
|
|
||||||
|
|
||||||
// wfunc user32 DefWindowProcW uintptr t_UINT t_WPARAM t_LPARAM t_LRESULT,noerr
|
|
||||||
|
|
||||||
// this one doesn't technically return void but let's pretend it does since its return value is irrelevant/not indicative of anything useful
|
|
||||||
// wfunc user32 ShowWindow uintptr uintptr void
|
|
||||||
|
|
||||||
// wfunc user32 SendMessageW uintptr t_UINT t_WPARAM t_LPARAM t_LRESULT,noerr
|
|
||||||
// wfunc user32 UpdateWindow uintptr uintptr
|
|
||||||
// wfunc user32 DestroyWindow uintptr uintptr
|
|
||||||
// wfunc user32 PostQuitMessage uintptr void
|
|
||||||
// wfunc user32 GetClientRect uintptr *s_RECT uintptr
|
|
||||||
// wfunc user32 SetParent uintptr uintptr uintptr
|
|
||||||
// wfunc gdi32 GetTextMetricsW uintptr *s_TEXTMETRICW uintptr
|
|
||||||
|
|
||||||
// TODO int here will be wrong on 64-bit systems
|
|
||||||
// wfunc kernel32 MulDiv int int int int,noerr
|
|
||||||
// wfunc user32 MoveWindow uintptr int int int int t_BOOL uintptr
|
|
||||||
|
|
||||||
// wfunc user32 GetDC uintptr uintptr
|
|
||||||
// wfunc gdi32 SelectObject uintptr uintptr uintptr
|
|
||||||
// wfunc user32 ReleaseDC uintptr uintptr uintptr
|
|
||||||
// wfunc user32 IsChild uintptr uintptr uintptr,noerr
|
|
||||||
// wfunc kernel32 CreateActCtxW *s_ACTCTXW uintptr
|
|
||||||
// wfunc kernel32 ActivateActCtx uintptr *t_ULONG_PTR uintptr
|
|
|
@ -1 +0,0 @@
|
||||||
go run zwinconstgen.go . 386 "$@" && go run zwinconstgen.go . amd64 "$@"
|
|
|
@ -1,414 +0,0 @@
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
// 24 may 2014
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"go/token"
|
|
||||||
"go/ast"
|
|
||||||
"go/parser"
|
|
||||||
"sort"
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"text/template"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getPackage(path string) (pkg *ast.Package) {
|
|
||||||
fileset := token.NewFileSet() // parser.ParseDir() actually writes to this; not sure why it doesn't return one instead
|
|
||||||
filter := func(i os.FileInfo) bool {
|
|
||||||
return strings.HasSuffix(i.Name(), "_windows.go")
|
|
||||||
}
|
|
||||||
pkgs, err := parser.ParseDir(fileset, path, filter, parser.AllErrors | parser.ParseComments)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if len(pkgs) != 1 {
|
|
||||||
panic("more than one package found")
|
|
||||||
}
|
|
||||||
for k, _ := range pkgs { // get the sole key
|
|
||||||
pkg = pkgs[k]
|
|
||||||
}
|
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
type walker struct {
|
|
||||||
desired func(string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var known = map[string]string{}
|
|
||||||
var unknown = map[string]struct{}{}
|
|
||||||
|
|
||||||
func (w *walker) Visit(node ast.Node) ast.Visitor {
|
|
||||||
switch n := node.(type) {
|
|
||||||
case *ast.Ident: // constant or structure?
|
|
||||||
if w.desired(n.Name) {
|
|
||||||
if n.Obj != nil {
|
|
||||||
delete(unknown, n.Name)
|
|
||||||
kind := n.Obj.Kind.String()
|
|
||||||
if known[n.Name] != "" && known[n.Name] != kind {
|
|
||||||
panic(n.Name + "(" + kind + ") already known to be a " + known[n.Name])
|
|
||||||
}
|
|
||||||
known[n.Name] = kind
|
|
||||||
} else if _, ok := known[n.Name]; !ok { // only if not known
|
|
||||||
unknown[n.Name] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func gatherNames(pkg *ast.Package) {
|
|
||||||
desired := func(name string) bool {
|
|
||||||
return strings.HasPrefix(name, "c_") || // constants
|
|
||||||
strings.HasPrefix(name, "s_") // structs
|
|
||||||
}
|
|
||||||
for _, f := range pkg.Files {
|
|
||||||
for _, d := range f.Decls {
|
|
||||||
ast.Walk(&walker{desired}, d)
|
|
||||||
}
|
|
||||||
for _, c := range f.Comments {
|
|
||||||
for _, cc := range c.List {
|
|
||||||
readComment(cc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var funcs []string
|
|
||||||
var dlls = map[string]struct{}{}
|
|
||||||
|
|
||||||
// parse comments of the form "// wfunc dll Name argtypes {ret[,noerr]|void}"
|
|
||||||
// TODO clean this up
|
|
||||||
func readComment(c *ast.Comment) {
|
|
||||||
words := strings.Split(c.Text, " ")[1:] // strip leading //
|
|
||||||
if len(words) <= 0 || words[0] != "wfunc" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dll := words[1]
|
|
||||||
dlls[dll] = struct{}{}
|
|
||||||
name := words[2]
|
|
||||||
args := make([]string, 0, len(words))
|
|
||||||
for i := 3; i < len(words) - 1; i++ {
|
|
||||||
args = append(args, words[i])
|
|
||||||
}
|
|
||||||
ret := words[len(words) - 1]
|
|
||||||
|
|
||||||
funcs = append(funcs, fmt.Sprintf("var fv_%s = %s.NewProc(%q)", name, dll, name))
|
|
||||||
|
|
||||||
r1 := "r1"
|
|
||||||
err := "err"
|
|
||||||
assign := ":="
|
|
||||||
|
|
||||||
r := rune('a')
|
|
||||||
argspec := ""
|
|
||||||
for _, t := range args {
|
|
||||||
argspec += fmt.Sprintf("%c %s, ", r, t)
|
|
||||||
r++
|
|
||||||
}
|
|
||||||
retspec := ""
|
|
||||||
if ret != "void" {
|
|
||||||
r := strings.Split(ret, ",")
|
|
||||||
retspec = "(" + r[0]
|
|
||||||
if len(r) > 1 && r[1] == "noerr" {
|
|
||||||
err = "_"
|
|
||||||
} else {
|
|
||||||
retspec += ", error"
|
|
||||||
}
|
|
||||||
retspec += ")"
|
|
||||||
} else {
|
|
||||||
r1 = "_"
|
|
||||||
err = "_"
|
|
||||||
assign = "="
|
|
||||||
}
|
|
||||||
funcs = append(funcs, fmt.Sprintf("func f_%s(%s) %s {", name, argspec, retspec))
|
|
||||||
|
|
||||||
call := fmt.Sprintf("\t%s, _, %s %s fv_%s.Call(", r1, err, assign, name)
|
|
||||||
r = rune('a')
|
|
||||||
for _, t := range args {
|
|
||||||
call += "uintptr("
|
|
||||||
if t[0] == '*' {
|
|
||||||
call += "unsafe.Pointer("
|
|
||||||
}
|
|
||||||
call += fmt.Sprintf("%c", r)
|
|
||||||
if t[0] == '*' {
|
|
||||||
call += ")"
|
|
||||||
}
|
|
||||||
call += "), "
|
|
||||||
r++
|
|
||||||
}
|
|
||||||
call += ")"
|
|
||||||
funcs = append(funcs, call)
|
|
||||||
|
|
||||||
if ret != "void" {
|
|
||||||
r := strings.Split(ret, ",")
|
|
||||||
retspec = "return (" + r[0] + ")("
|
|
||||||
if r[0][0] == '*' {
|
|
||||||
retspec += "unsafe.Pointer("
|
|
||||||
}
|
|
||||||
retspec += "r1"
|
|
||||||
if r[0][0] == '*' {
|
|
||||||
retspec += ")"
|
|
||||||
}
|
|
||||||
retspec += ")"
|
|
||||||
if len(r) > 1 && r[1] == "noerr" {
|
|
||||||
// do nothing
|
|
||||||
} else {
|
|
||||||
retspec += ", err"
|
|
||||||
}
|
|
||||||
funcs = append(funcs, retspec)
|
|
||||||
}
|
|
||||||
|
|
||||||
funcs = append(funcs, "}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// for backwards compatibiilty reasons, Windows defines GetWindowLongPtr()/SetWindowLongPtr() as a macro which expands to GetWindowLong()/SetWindowLong() on 32-bit systems
|
|
||||||
// we'll just simulate that here
|
|
||||||
var gwlpNames = map[string]string{
|
|
||||||
"386": "etWindowLongW",
|
|
||||||
"amd64": "etWindowLongPtrW",
|
|
||||||
}
|
|
||||||
|
|
||||||
// in reality these use LONG_PTR for the actual values; LONG_PTR is a signed value, but for our use case it doesn't really matter
|
|
||||||
func genGetSetWindowLongPtr(targetarch string) {
|
|
||||||
name := gwlpNames[targetarch]
|
|
||||||
|
|
||||||
funcs = append(funcs, fmt.Sprintf("var fv_GetWindowLongPtrW = user32.NewProc(%q)", "G" + name))
|
|
||||||
funcs = append(funcs, "func f_GetWindowLongPtrW(hwnd uintptr, which uintptr) uintptr {")
|
|
||||||
funcs = append(funcs, "\tres, _, _ := fv_GetWindowLongPtrW.Call(hwnd, which)")
|
|
||||||
funcs = append(funcs, "\treturn res")
|
|
||||||
funcs = append(funcs, "}")
|
|
||||||
|
|
||||||
funcs = append(funcs, fmt.Sprintf("var fv_SetWindowLongPtrW = user32.NewProc(%q)", "S" + name))
|
|
||||||
funcs = append(funcs, "func f_SetWindowLongPtrW(hwnd uintptr, which uintptr, value uintptr) {")
|
|
||||||
funcs = append(funcs, "\tfv_SetWindowLongPtrW.Call(hwnd, which, value)")
|
|
||||||
funcs = append(funcs, "}")
|
|
||||||
}
|
|
||||||
|
|
||||||
const outTemplate = `package main
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"bytes"
|
|
||||||
"reflect"
|
|
||||||
"go/format"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
// #define UNICODE
|
|
||||||
// #define _UNICODE
|
|
||||||
// #define STRICT
|
|
||||||
// #define STRICT_TYPED_ITEMIDS
|
|
||||||
// /* get Windows version right; right now Windows XP */
|
|
||||||
// #define WINVER 0x0501
|
|
||||||
// #define _WIN32_WINNT 0x0501
|
|
||||||
// #define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
|
|
||||||
// #define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
|
|
||||||
// #define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
|
|
||||||
// #include <windows.h>
|
|
||||||
// #include <commctrl.h>
|
|
||||||
// #include <stdint.h>
|
|
||||||
{{range .Consts}}// uintptr_t {{.}} = (uintptr_t) ({{noprefix .}});
|
|
||||||
{{end}}import "C" // notice the lack of newline in the template
|
|
||||||
// MinGW will generate handle pointers as pointers to some structure type under some conditions I don't fully understand; here's full overrides
|
|
||||||
var handleOverrides = []string{
|
|
||||||
"HWND",
|
|
||||||
"HINSTANCE",
|
|
||||||
"HICON",
|
|
||||||
"HCURSOR",
|
|
||||||
"HBRUSH",
|
|
||||||
"HMENU",
|
|
||||||
// These are all pointers to functions; handle them identically to handles.
|
|
||||||
"WNDPROC",
|
|
||||||
}
|
|
||||||
func winName(t reflect.Type) string {
|
|
||||||
for _, s := range handleOverrides {
|
|
||||||
if strings.Contains(t.Name(), s) {
|
|
||||||
return "uintptr"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.UnsafePointer:
|
|
||||||
return "uintptr"
|
|
||||||
case reflect.Ptr:
|
|
||||||
return "*" + winName(t.Elem())
|
|
||||||
case reflect.Struct:
|
|
||||||
// the t.Name() will be the cgo-mangled name; get the original name out
|
|
||||||
parts := strings.Split(t.Name(), "_")
|
|
||||||
part := parts[len(parts) - 1]
|
|
||||||
// many Windows API types have struct tagXXX as their declarator
|
|
||||||
// if you wonder why, see http://blogs.msdn.com/b/oldnewthing/archive/2008/03/26/8336829.aspx?Redirected=true
|
|
||||||
if strings.HasPrefix(part, "tag") {
|
|
||||||
part = part[3:]
|
|
||||||
}
|
|
||||||
return "s_" + part
|
|
||||||
case reflect.Array:
|
|
||||||
return fmt.Sprintf("[%d]%s", t.Len(), winName(t.Elem()))
|
|
||||||
}
|
|
||||||
return t.Kind().String()
|
|
||||||
}
|
|
||||||
func main() {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
fmt.Fprintln(buf, "package ui")
|
|
||||||
fmt.Fprintln(buf, "import (")
|
|
||||||
fmt.Fprintln(buf, "\t\"syscall\"")
|
|
||||||
fmt.Fprintln(buf, "\t\"unsafe\"")
|
|
||||||
fmt.Fprintln(buf, ")")
|
|
||||||
|
|
||||||
// constants
|
|
||||||
{{range .Consts}} fmt.Fprintf(buf, "const %s = %d\n", {{printf "%q" .}}, C.{{.}})
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// structures
|
|
||||||
var t reflect.Type
|
|
||||||
{{range .Structs}} t = reflect.TypeOf(C.{{noprefix .}}{})
|
|
||||||
fmt.Fprintf(buf, "type %s struct {\n", {{printf "%q" .}})
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
fmt.Fprintf(buf, "\t%s %s\n", t.Field(i).Name, winName(t.Field(i).Type))
|
|
||||||
}
|
|
||||||
fmt.Fprintf(buf, "}\n")
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// let's generate names for window procedure types
|
|
||||||
fmt.Fprintf(buf, "\n")
|
|
||||||
fmt.Fprintf(buf, "type t_UINT %s\n", winName(reflect.TypeOf(C.UINT(0))))
|
|
||||||
fmt.Fprintf(buf, "type t_WPARAM %s\n", winName(reflect.TypeOf(C.WPARAM(0))))
|
|
||||||
fmt.Fprintf(buf, "type t_LPARAM %s\n", winName(reflect.TypeOf(C.LPARAM(0))))
|
|
||||||
fmt.Fprintf(buf, "type t_LRESULT %s\n", winName(reflect.TypeOf(C.LRESULT(0))))
|
|
||||||
fmt.Fprintf(buf, "type t_UINT_PTR %s\n", winName(reflect.TypeOf(C.UINT_PTR(0))))
|
|
||||||
fmt.Fprintf(buf, "type t_DWORD_PTR %s\n", winName(reflect.TypeOf(C.DWORD_PTR(0))))
|
|
||||||
// and one for initCommonControls()
|
|
||||||
fmt.Fprintf(buf, "type t_ULONG_PTR %s\n", winName(reflect.TypeOf(C.ULONG_PTR(0))))
|
|
||||||
// and one for GetMessageW()
|
|
||||||
fmt.Fprintf(buf, "type t_BOOL %s\n", winName(reflect.TypeOf(C.BOOL(0))))
|
|
||||||
|
|
||||||
// functions
|
|
||||||
{{range .Funcs}} fmt.Fprintf(buf, "%s\n", {{printf "%q" .}})
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// DLLs
|
|
||||||
{{range .DLLs}} fmt.Fprintf(buf, "var %s = syscall.NewLazyDLL(%q)\n", {{printf "%q" .}}, {{printf "%s.dll" . | printf "%q"}})
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
// and finally done
|
|
||||||
res, err := format.Source(buf.Bytes())
|
|
||||||
if err != nil { panic(err.Error() + "\n" + string(buf.Bytes())) }
|
|
||||||
fmt.Printf("%s", res)
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
type templateArgs struct {
|
|
||||||
Consts []string
|
|
||||||
Structs []string
|
|
||||||
Funcs []string
|
|
||||||
DLLs []string
|
|
||||||
}
|
|
||||||
|
|
||||||
var templateFuncs = template.FuncMap{
|
|
||||||
"noprefix": func(s string) string {
|
|
||||||
return s[2:]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) < 3 {
|
|
||||||
panic("usage: " + os.Args[0] + " path goarch [go-command-options...]")
|
|
||||||
}
|
|
||||||
pkgpath := os.Args[1]
|
|
||||||
targetarch := os.Args[2]
|
|
||||||
if _, ok := gwlpNames[targetarch]; !ok {
|
|
||||||
panic("unknown target windows/" + targetarch)
|
|
||||||
}
|
|
||||||
goopts := os.Args[3:] // valid if len(os.Args) == 3; in that case this will just be a slice of length zero
|
|
||||||
|
|
||||||
pkg := getPackage(pkgpath)
|
|
||||||
gatherNames(pkg)
|
|
||||||
|
|
||||||
// if we still have some known, I didn't clean things up completely
|
|
||||||
if len(known) > 0 {
|
|
||||||
knowns := ""
|
|
||||||
for ident, kind := range known {
|
|
||||||
if kind != "var" && kind != "const" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
knowns += "\n" + ident + " (" + kind + ")"
|
|
||||||
}
|
|
||||||
panic("error: the following are still known!" + knowns) // has a newline already
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep sorted for git
|
|
||||||
consts := make([]string, 0, len(unknown))
|
|
||||||
structs := make([]string, 0, len(unknown))
|
|
||||||
sorteddlls := make([]string, 0, len(dlls))
|
|
||||||
for ident, _ := range unknown {
|
|
||||||
if strings.HasPrefix(ident, "s_") {
|
|
||||||
structs = append(structs, ident)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
consts = append(consts, ident)
|
|
||||||
}
|
|
||||||
for dll, _ := range dlls {
|
|
||||||
sorteddlls = append(sorteddlls, dll)
|
|
||||||
}
|
|
||||||
sort.Strings(consts)
|
|
||||||
sort.Strings(structs)
|
|
||||||
sort.Strings(sorteddlls)
|
|
||||||
|
|
||||||
// and finally
|
|
||||||
genGetSetWindowLongPtr(targetarch)
|
|
||||||
|
|
||||||
// thanks to james4k in irc.freenode.net/#go-nuts
|
|
||||||
tmpdir, err := ioutil.TempDir("", "windowsconstgen")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
genoutname := filepath.Join(tmpdir, "gen.go")
|
|
||||||
f, err := os.Create(genoutname)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t := template.Must(template.New("winconstgenout").Funcs(templateFuncs).Parse(outTemplate))
|
|
||||||
err = t.Execute(f, &templateArgs{
|
|
||||||
Consts: consts,
|
|
||||||
Structs: structs,
|
|
||||||
Funcs: funcs,
|
|
||||||
DLLs: sorteddlls,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command("go", "run")
|
|
||||||
cmd.Args = append(cmd.Args, goopts...) // valid if len(goopts) == 0; in that case this will just be a no-op
|
|
||||||
cmd.Args = append(cmd.Args, genoutname)
|
|
||||||
f, err = os.Create(filepath.Join(pkgpath, "zconstants_windows_" + targetarch + ".go"))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
cmd.Stdout = f
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
// we need to preserve the environment EXCEPT FOR the variables we're overriding
|
|
||||||
// thanks to raggi and smw in irc.freenode.net/#go-nuts
|
|
||||||
for _, ev := range os.Environ() {
|
|
||||||
if strings.HasPrefix(ev, "GOOS=") ||
|
|
||||||
strings.HasPrefix(ev, "GOARCH=") ||
|
|
||||||
strings.HasPrefix(ev, "CGO_ENABLED=") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cmd.Env = append(cmd.Env, ev)
|
|
||||||
}
|
|
||||||
cmd.Env = append(cmd.Env,
|
|
||||||
"GOOS=windows",
|
|
||||||
"GOARCH=" + targetarch,
|
|
||||||
"CGO_ENABLED=1") // needed as it's not set by default in cross-compiles
|
|
||||||
err = cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
// TODO find a way to get the exit code
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO remove the temporary directory
|
|
||||||
}
|
|
Loading…
Reference in New Issue