diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..e04ec439 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,8 @@ +{ + "postCreateCommand": "/workspaces/wasm-git/.devcontainer/post-create.sh", + "customizations": { + "vscode": { + "extensions": ["dtsvet.vscode-wasm","ms-vscode.cpptools-extension-pack"] + } + } +} diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 00000000..d1c9e047 --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,29 @@ +#!/bin/bash +curl https://wasmtime.dev/install.sh -sSf | bash +npm install +sh setup.sh + +git clone https://github.com/emscripten-core/emsdk.git +cd emsdk +git pull +./emsdk install latest +./emsdk activate latest +cd .. + +sudo apt update +sudo apt install -y clang-12 +sudo apt install -y lld-12 +sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-12 100 +sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-12 100 + +sudo ln -s /usr/bin/wasm-ld-12 /usr/bin/wasm-ld + +cd wasibuild + +wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0-linux.tar.gz +tar -xvzf wasi-sdk-20.0-linux.tar.gz +sudo cp -r wasi-sdk-20.0/lib/clang/16/lib/wasi /usr/lib/llvm-12/lib/clang/12.0.0/lib/ + +cd .. + +curl https://wasmtime.dev/install.sh -sSf | bash diff --git a/.gitignore b/.gitignore index e268c01f..a07c52d8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ package .idea emsdk nodefsclonetest -google-chrome-stable_current_amd64.deb \ No newline at end of file +google-chrome-stable_current_amd64.deb diff --git a/emscriptenbuild/build.sh b/emscriptenbuild/build.sh index 4a90fd2b..2cffbe2d 100755 --- a/emscriptenbuild/build.sh +++ b/emscriptenbuild/build.sh @@ -28,8 +28,8 @@ elif [ "$1" == "Debug-async" ]; then POST_JS="--post-js $(pwd)/post-async.js" fi -# Before building, remove any ../libgit2/src/transports/emscriptenhttp.c left from running setup.sh +# Before building, remove any ../libgit2/src/ transports/emscriptenhttp.c left from running setup.sh [ -f "../libgit2/src/libgit2/transports/emscriptenhttp-async.c" ] && rm ../libgit2/src/libgit2/transports/emscriptenhttp-async.c -emcmake cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_FLAGS="$EXTRA_CMAKE_C_FLAGS --pre-js $(pwd)/pre.js $POST_JS -s \"EXTRA_EXPORTED_RUNTIME_METHODS=['FS','callMain']\" -lnodefs.js -lidbfs.js -s INVOKE_RUN=0 -s ALLOW_MEMORY_GROWTH=1 -s STACK_SIZE=131072" -DREGEX_BACKEND=regcomp -DSONAME=OFF -DUSE_HTTPS=OFF -DBUILD_SHARED_LIBS=OFF -DTHREADSAFE=OFF -DUSE_SSH=OFF -DBUILD_CLAR=OFF -DBUILD_EXAMPLES=ON ../libgit2 +emcmake cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_FLAGS="$EXTRA_CMAKE_C_FLAGS --pre-js $(pwd)/pre.js $POST_JS -s \"EXTRA_EXPORTED_RUNTIME_METHODS=['FS','callMain']\" -sFORCE_FILESYSTEM -sEXPORT_ES6 -sWASMFS -s INVOKE_RUN=0 -s ALLOW_MEMORY_GROWTH=1 -s STACK_SIZE=131072" -DREGEX_BACKEND=regcomp -DSONAME=OFF -DUSE_HTTPS=OFF -DBUILD_SHARED_LIBS=OFF -DTHREADSAFE=OFF -DUSE_SSH=OFF -DBUILD_CLAR=OFF -DBUILD_EXAMPLES=ON ../libgit2 emmake make lg2 diff --git a/package.json b/package.json index 513875c0..19894294 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "wasm-git", "version": "0.0.11", + "type": "module", "author": { "name": "Peter Salomonsen", "url": "https://petersalomonsen.com" diff --git a/setup.sh b/setup.sh index b538bb60..699037c1 100755 --- a/setup.sh +++ b/setup.sh @@ -1,3 +1,5 @@ +#!/bin/bash + curl -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.7.1.tar.gz --output libgit2.tar.gz tar -xzf libgit2.tar.gz mv libgit2-1.7.1 libgit2 diff --git a/setup_wasi.sh b/setup_wasi.sh new file mode 100755 index 00000000..6e1a9842 --- /dev/null +++ b/setup_wasi.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +curl -L https://github.com/libgit2/libgit2/archive/refs/tags/v1.7.1.tar.gz --output libgit2.tar.gz +tar -xzf libgit2.tar.gz +mv libgit2-1.7.1 libgit2 +rm libgit2.tar.gz +rm libgit2/src/libgit2/transports/http.c +rm libgit2/src/libgit2/streams/socket.c + +cp -r libgit2patchedfiles/examples/* libgit2/examples/ diff --git a/test/checkout.spec.js b/test/checkout.spec.js index cb5fc32d..dc6ed9bd 100644 --- a/test/checkout.spec.js +++ b/test/checkout.spec.js @@ -1,5 +1,5 @@ -const lgPromise = require('./common.js').lgPromise; -const assert = require('assert'); +import { lgPromise } from './common.js'; +import assert from 'assert'; describe('git checkout', () => { beforeEach(async () => { diff --git a/test/common.js b/test/common.js index 10a3d0bf..af0672bd 100644 --- a/test/common.js +++ b/test/common.js @@ -1,18 +1 @@ -module.exports = { - lgPromise: new Promise(resolve => { - const lg = require('./lg2.js'); - lg.onRuntimeInitialized = () => { - const FS = lg.FS; - const MEMFS = FS.filesystems.MEMFS; - - FS.mkdir('/working'); - FS.mount(MEMFS, { }, '/working'); - FS.chdir('/working'); - - FS.writeFile('/home/web_user/.gitconfig', '[user]\n' + - 'name = Test User\n' + - 'email = test@example.com'); - resolve(lg); - }; - }) -} +export const lgPromise = await import('./lg2.js').then(r => r.default()); diff --git a/test/conflict.spec.js b/test/conflict.spec.js index ce5e1cfb..328919a1 100644 --- a/test/conflict.spec.js +++ b/test/conflict.spec.js @@ -1,5 +1,5 @@ -const lgPromise = require('./common.js').lgPromise; -const assert = require('assert'); +import { lgPromise } from './common.js'; +import assert from 'assert'; describe('conflicts', function() { beforeEach(async () => { diff --git a/test/fetch.spec.js b/test/fetch.spec.js index ecf8f766..723d9853 100644 --- a/test/fetch.spec.js +++ b/test/fetch.spec.js @@ -1,5 +1,5 @@ -const assert = require('assert'); -const lgPromise = require('./common.js').lgPromise; +import { lgPromise } from './common.js'; +import assert from 'assert'; describe('git fetch', () => { beforeEach(async () => { diff --git a/test/hellowasmfs.spec.js b/test/hellowasmfs.spec.js new file mode 100644 index 00000000..e6a3385c --- /dev/null +++ b/test/hellowasmfs.spec.js @@ -0,0 +1,18 @@ +import { lgPromise } from './common.js'; + +describe.only('hello wasmfs', () => { + it.only('hello wasmfs', async () => { + const lg = await lgPromise; + const FS = lg.FS; + + FS.mkdir('/test'); + FS.chdir('/test'); + lg.callMain(['init', '.']); + lg.callMain(['config', 'user.name', 'test']); + lg.callMain(['config', 'user.email', 'test@example.com']); + FS.writeFile('test.txt', 'abcdef'); + lg.callMain(['add', 'test.txt']); + lg.callMain(['commit', '-m', 'test commit']); + lg.callMain(['log']); + }); +}); diff --git a/test/nodefs.spec.js b/test/nodefs.spec.js index af972d7c..ad67dec2 100644 --- a/test/nodefs.spec.js +++ b/test/nodefs.spec.js @@ -1,6 +1,6 @@ -const lgPromise = require('./common.js').lgPromise; -const assert = require('assert'); -const { rmSync } = require('fs'); +import { lgPromise } from './common.js'; +import assert from 'assert'; +import {rmSync} from 'fs'; describe('nodefs', function () { this.timeout(20000); diff --git a/test/revert.spec.js b/test/revert.spec.js index 06cf35be..6a987f96 100644 --- a/test/revert.spec.js +++ b/test/revert.spec.js @@ -1,5 +1,5 @@ -const lgPromise = require('./common.js').lgPromise; -const assert = require('assert'); +import { lgPromise } from './common.js'; +import assert from 'assert'; describe('git revert', () => { beforeEach(async () => { diff --git a/wasibuild/.gitignore b/wasibuild/.gitignore new file mode 100644 index 00000000..1cf43315 --- /dev/null +++ b/wasibuild/.gitignore @@ -0,0 +1,13 @@ +CMake* +src +deps +examples +libgit2* +include +Makefile +*.cmake +*.a +tests +wasi-sdk* +!wasi_toolchain.cmake +CopyOfCMakeCache.txt \ No newline at end of file diff --git a/wasibuild/build.sh b/wasibuild/build.sh new file mode 100755 index 00000000..0a3ba819 --- /dev/null +++ b/wasibuild/build.sh @@ -0,0 +1,14 @@ +#!/bin/bash +BUILD_TYPE=Debug + +# Set build type to Release for release +if [ "$1" == "Release" ]; then + BUILD_TYPE=Release + EXTRA_CMAKE_C_FLAGS="-Oz" +fi + +clang --target=wasm32-wasi --sysroot=wasi-sdk-20.0/share/wasi-sysroot -D_WASI_EMULATED_MMAN -Iwasi_mocks -c wasi_mocks/pwd.c -o wasi_mocks/pwd.o +clang --target=wasm32-wasi --sysroot=wasi-sdk-20.0/share/wasi-sysroot -D_WASI_EMULATED_MMAN -Iwasi_mocks -c wasi_mocks/posix_mocks.c -o wasi_mocks/posix_mocks.o +cmake -DCMAKE_TOOLCHAIN_FILE=`pwd`/wasi_toolchain.cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_FLAGS="$EXTRA_CMAKE_C_FLAGS" -DREGEX_BACKEND=regcomp -DSONAME=OFF -DUSE_HTTPS=OFF -DBUILD_SHARED_LIBS=OFF -DUSE_THREADS=OFF -DUSE_SSH=OFF -DBUILD_CLAR=OFF -DBUILD_EXAMPLES=ON ../libgit2 +make lg2 VERBOSE=1 +mv examples/lg2 lg2.wasm diff --git a/wasibuild/wasi_mocks/.gitignore b/wasibuild/wasi_mocks/.gitignore new file mode 100644 index 00000000..15309787 --- /dev/null +++ b/wasibuild/wasi_mocks/.gitignore @@ -0,0 +1 @@ +*.o \ No newline at end of file diff --git a/wasibuild/wasi_mocks/posix_mocks.c b/wasibuild/wasi_mocks/posix_mocks.c new file mode 100644 index 00000000..2634819f --- /dev/null +++ b/wasibuild/wasi_mocks/posix_mocks.c @@ -0,0 +1,86 @@ +#include "posix_mocks.h" +#include + +char *realpath(const char *path, char *resolved_path) { + if (resolved_path) { + // Mock implementation - return an empty string or some mock path + resolved_path[0] = '\0'; + } + return resolved_path; +} + +pid_t getpid(void) { + return 1; // Mock PID +} + +pid_t getppid(void) { + return 1; // Mock Parent PID +} + +pid_t getpgid(pid_t pid) { + return 1; // Mock PGID +} + +pid_t getsid(pid_t pid) { + return 1; // Mock SID +} + +uid_t getuid(void) { + return 0; // Mock UID +} + +gid_t getgid(void) { + return 1000; // Mock GID +} + +int chmod(const char *pathname, mode_t mode) { + return 0; // Mock success +} + +uid_t geteuid(void) { + return 0; // Mock effective UID +} + +struct passwd *getpwuid(uid_t uid) { + static struct passwd dummy_passwd; + // Populate dummy_passwd with mock data + return &dummy_passwd; +} + +int git_socket_stream_global_init(void) { + return 0; // Mock success +} + +void *git_socket_stream_new(int a, int b, int c) { + return NULL; // Mock return +} + +void *git_smart_subtransport_http(void) { + return NULL; // Mock return +} + +int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { + if (!pwd || !buf || buflen < sizeof(struct passwd)) { + errno = ERANGE; + return ERANGE; + } + + // You can fill in the `pwd` structure with mock data as needed + pwd->pw_name = "codespace"; + pwd->pw_passwd = "mockpassword"; + pwd->pw_uid = uid; + pwd->pw_gid = 0; // Mock GID + pwd->pw_gecos = "Mock User"; + pwd->pw_dir = "/home/codespace"; + pwd->pw_shell = "/bin/sh"; + + // Copy the structure to the provided buffer + memcpy(buf, pwd, sizeof(struct passwd)); + + // Set the result + *result = (struct passwd *)buf; + + return 0; // Return success +} + +// ... Add more function implementations as needed ... diff --git a/wasibuild/wasi_mocks/posix_mocks.h b/wasibuild/wasi_mocks/posix_mocks.h new file mode 100644 index 00000000..80adcdfe --- /dev/null +++ b/wasibuild/wasi_mocks/posix_mocks.h @@ -0,0 +1,24 @@ +#ifndef POSIX_MOCKS_H +#define POSIX_MOCKS_H + +#include +#include +#include "pwd.h" + +// Mock function declarations +char *realpath(const char *path, char *resolved_path); +pid_t getpid(void); +pid_t getppid(void); +pid_t getpgid(pid_t pid); +pid_t getsid(pid_t pid); +uid_t getuid(void); +gid_t getgid(void); +int chmod(const char *pathname, mode_t mode); +uid_t geteuid(void); +struct passwd *getpwuid(uid_t uid); +int git_socket_stream_global_init(void); +void *git_socket_stream_new(int a, int b, int c); +void *git_smart_subtransport_http(void); +int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result); + +#endif // POSIX_MOCKS_H diff --git a/wasibuild/wasi_mocks/pwd.c b/wasibuild/wasi_mocks/pwd.c new file mode 100644 index 00000000..c6399bd2 --- /dev/null +++ b/wasibuild/wasi_mocks/pwd.c @@ -0,0 +1,31 @@ +#include +#include + +// Mock structure similar to 'struct passwd' in pwd.h +struct passwd { + char *pw_name; // user's name + char *pw_passwd; // user's password + uid_t pw_uid; // user's user ID + gid_t pw_gid; // user's group ID + char *pw_gecos; // user's real name + char *pw_dir; // user's home directory + char *pw_shell; // user's shell program +}; + +// Mock function similar to 'getpwnam' in pwd.h +struct passwd *getpwnam(const char *name) { + static struct passwd mock_user; + + // Hardcoded user information + mock_user.pw_name = "codespace"; + mock_user.pw_passwd = "mockpassword"; + mock_user.pw_uid = 0; + mock_user.pw_gid = 0; + mock_user.pw_gecos = "Mock User"; + mock_user.pw_dir = "/home/codespace"; + mock_user.pw_shell = "/bin/sh"; + + return &mock_user; +} + +// ... other mock functions as needed ... diff --git a/wasibuild/wasi_mocks/pwd.h b/wasibuild/wasi_mocks/pwd.h new file mode 100644 index 00000000..f25bb217 --- /dev/null +++ b/wasibuild/wasi_mocks/pwd.h @@ -0,0 +1,20 @@ +#ifndef MOCK_PWD_H +#define MOCK_PWD_H + +#include + +// Mock structure similar to 'struct passwd' in pwd.h +struct passwd { + char *pw_name; // user's name + char *pw_passwd; // user's password + uid_t pw_uid; // user's user ID + gid_t pw_gid; // user's group ID + char *pw_gecos; // user's real name + char *pw_dir; // user's home directory + char *pw_shell; // user's shell program +}; + +// Mock function declaration +struct passwd *getpwnam(const char *name); + +#endif // MOCK_PWD_H diff --git a/wasibuild/wasi_toolchain.cmake b/wasibuild/wasi_toolchain.cmake new file mode 100644 index 00000000..9681e58c --- /dev/null +++ b/wasibuild/wasi_toolchain.cmake @@ -0,0 +1,16 @@ +SET(CMAKE_C_COMPILER clang) +SET(CMAKE_FIND_ROOT_PATH ${CMAKE_CURRENT_LIST_DIR}/wasi-sdk-20.0/share/wasi-sysroot/) +SET(CMAKE_SYSROOT ${CMAKE_CURRENT_LIST_DIR}/wasi-sdk-20.0/share/wasi-sysroot/) +set(CMAKE_C_FLAGS "--target=wasm32-wasi -Wl,--initial-memory=268435456 -I${CMAKE_CURRENT_LIST_DIR}/wasi_mocks -D_WASI_EMULATED_MMAN ${CMAKE_C_FLAGS}") + +set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES "${CMAKE_SYSROOT}/include") + +set(CMAKE_C_FLAGS_DEBUG "") + +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_AR "llvm-ar" CACHE FILEPATH "llvm-ar") +set(CMAKE_RANLIB "llvm-ranlib" CACHE FILEPATH "llvm-ranlib") + +set(CMAKE_EXE_LINKER_FLAGS "-lwasi-emulated-mman ${CMAKE_CURRENT_LIST_DIR}/wasi_mocks/pwd.o ${CMAKE_CURRENT_LIST_DIR}/wasi_mocks/posix_mocks.o ${CMAKE_EXE_LINKER_FLAGS}") \ No newline at end of file