diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..dad93e8 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,38 @@ +name: Build and test the esy package + +on: + - push + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: + - macos-13 + - macos-latest + - ubuntu-latest + - windows-latest + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Use Node.js + uses: actions/setup-node@v3 + + - name: Install esy, verdaccio and esy-package + run: npm install -g esy esy-package + + - name: Run end-to-end tests on the package + run: DEBUG=bale*,verdaccio* esy-package + shell: bash + env: + SHELL: "bash.exe" # HACK!! For some weird reason, $SHELL in Github actions' Windows' bash shell doesn't set this environment variable. We rely on it to figure if path normalisation is necessary on Gitbash Windows + + - uses: actions/upload-artifact@v3 + with: + name: release + path: package.tar.gz diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c99af7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*~ +node_modules +_esy +_esy-package +.log +package.tar.gz +package.tgz +verdaccio-storage \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..79a5aba --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +//localhost:4873/:_authToken="+vVrDzq8gudTDDEoQOS+PQ==" \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..54da9c5 --- /dev/null +++ b/Readme.md @@ -0,0 +1,139 @@ +![Build and test the esy +package](https://github.com/ManasJayanth/esy-packages-template/workflows/Build%20and%20test%20the%20esy%20package/badge.svg) + +# esy-zlib + +`esy-zlib` is sample package, [`zlib`](https://www.zlib.net/), packaged for [`esy`](https://esy.sh/). + +## Why +`esy` can not only fetch and install Reason and OCaml libraries and tools, +but also those written in C. This extends reproducibility benefits to +packages written in C, like `skia`, `libffi`, `pkg-config` +etc. Users don't have to install them separately, nor have to worry if +they have installed the correct version. Read more at the docs about +[benefits for opting for esy packages](https://esy.sh#TODO). + +## How to use `esy-zlib`? + +`esy-zlib` can be used from both NPM and directly from Github. + +### From NPM + +`esy-zlib` is deployed on NPM can be found +[here](https://www.npmjs.com/package/TODO). + +You can simply run `esy add esy-zlib` to install it, or specify it in +`package.json` and run `esy`. + +```diff +{ + "dependencies": { ++ "esy-zlib": "*" + } +} +``` + +### Directly from Github + +```json +{ + "dependencies": { + "esy-zlib": "esy-packages/esy-zlib" + } +} +``` + +i.e. `/` + +To use a specific commit, + +```diff + "dependencies": { ++ "esy-zlib": "esy-packages/esy-zlib#" + } +``` + +## How to package for esy? + +### For the experienced + +**The gist** +Specify the configure and build commands in `esy.build` property of +`esy.json` and the install step in `esy.install`. If the package +builds "in source", set `esy.buildsInSource` property to `true`. Use +`$cur__install` environment variable to set the install location. + +See [docs](TODO) for reference. + +The CI will take care of fetching the sources and creating an NPM +package for you. See [script.js](TODO) to see how it works. + +You can download it or auto publish via CI. + +### For beginners + +> Note: you'll need Node.js for this tutorial. If you're experienced +> with bash, you can use it instead. + +Fundamentally, packaging for esy works like in other Linux distros, +except ofcourse, such that packages become available on MacOS and +Windows too. + +You would typically have to specify the instructions to build the +package in the `esy.json`. For example, everyone's favourite http +tool, [curl](https://curl.se/), needs the following instructions ([as +described on their website](https://curl.se/docs/install.html)) + +```sh +./configure +make +make install +``` + +Many packages have similar instructions! + +Configure and build steps are specified in the `esy.build` property in +the `esy.json` and install steps in `esy.install`. Example, + +```json +{ + "esy": { + "build": [ + "./configure", + "make" + ], + "install": [ + "make install" + ] + } +} +``` + + +## Testing and making sure the package works as expected + +To test if the package works, we recommend an end-to-end test by +publishing it to local +[`verdaccio`](https://github.com/verdaccio/verdaccio), and using the +package with a `package.json` or `esy.json` depends on it. + +```json +{ + "dependencies": { + esy-zlib": "*" + } +} +``` + +And pointing `esy` to the local npm registry + +```sh +esy i --npm-registry http://localhost:4873 +esy b +``` + +If the package is a library, it's a good idea to write a small program +to actually check if the library works. Referring how the +corresponding package is being tested in Homebrew or Arch Linux. + +Checkout [ci-test.sh](./ci-test.sh) for reference, used on the CI. diff --git a/esy-test/.gitignore b/esy-test/.gitignore new file mode 100644 index 0000000..9edff42 --- /dev/null +++ b/esy-test/.gitignore @@ -0,0 +1,3 @@ +esy.lock +_esy +node_modules \ No newline at end of file diff --git a/esy-test/Readme.md b/esy-test/Readme.md new file mode 100644 index 0000000..cd9c728 --- /dev/null +++ b/esy-test/Readme.md @@ -0,0 +1 @@ +See [Readme in the root][../Readme.md#testing-and-making-sure-package-works-as-expected] diff --git a/esy-test/example.c b/esy-test/example.c new file mode 100644 index 0000000..d22e5f4 --- /dev/null +++ b/esy-test/example.c @@ -0,0 +1,552 @@ +/* example.c -- usage example of the zlib compression library + * Copyright (C) 1995-2006, 2011, 2016 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#if defined(_WIN32) && !defined(_CRT_SECURE_NO_WARNINGS) +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "zlib.h" +#include + +#ifdef STDC +# include +# include +#endif + +#if defined(VMS) +# define TESTFILE "foo-gz" +#elif defined(__riscos) && !defined(__TARGET_UNIXLIB__) +# define TESTFILE "foo/gz" +#else +# define TESTFILE "foo.gz" +#endif + +#define CHECK_ERR(err, msg) { \ + if (err != Z_OK) { \ + fprintf(stderr, "%s error: %d\n", msg, err); \ + exit(1); \ + } \ +} + +static z_const char hello[] = "hello, hello!"; +/* "hello world" would be more standard, but the repeated "hello" + * stresses the compression code better, sorry... + */ + +static const char dictionary[] = "hello"; +static uLong dictId; /* Adler32 value of the dictionary */ + +#ifdef Z_SOLO + +static void *myalloc(void *q, unsigned n, unsigned m) { + (void)q; + return calloc(n, m); +} + +static void myfree(void *q, void *p) { + (void)q; + free(p); +} + +static alloc_func zalloc = myalloc; +static free_func zfree = myfree; + +#else /* !Z_SOLO */ + +static alloc_func zalloc = (alloc_func)0; +static free_func zfree = (free_func)0; + +/* =========================================================================== + * Test compress() and uncompress() + */ +static void test_compress(Byte *compr, uLong comprLen, Byte *uncompr, + uLong uncomprLen) { + int err; + uLong len = (uLong)strlen(hello)+1; + + err = compress(compr, &comprLen, (const Bytef*)hello, len); + CHECK_ERR(err, "compress"); + + strcpy((char*)uncompr, "garbage"); + + err = uncompress(uncompr, &uncomprLen, compr, comprLen); + CHECK_ERR(err, "uncompress"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad uncompress\n"); + exit(1); + } else { + printf("uncompress(): %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Test read/write of .gz files + */ +static void test_gzio(const char *fname, Byte *uncompr, uLong uncomprLen) { +#ifdef NO_GZCOMPRESS + fprintf(stderr, "NO_GZCOMPRESS -- gz* functions cannot compress\n"); +#else + int err; + int len = (int)strlen(hello)+1; + gzFile file; + z_off_t pos; + + file = gzopen(fname, "wb"); + if (file == NULL) { + fprintf(stderr, "gzopen error\n"); + exit(1); + } + gzputc(file, 'h'); + if (gzputs(file, "ello") != 4) { + fprintf(stderr, "gzputs err: %s\n", gzerror(file, &err)); + exit(1); + } + if (gzprintf(file, ", %s!", "hello") != 8) { + fprintf(stderr, "gzprintf err: %s\n", gzerror(file, &err)); + exit(1); + } + gzseek(file, 1L, SEEK_CUR); /* add one zero byte */ + gzclose(file); + + file = gzopen(fname, "rb"); + if (file == NULL) { + fprintf(stderr, "gzopen error\n"); + exit(1); + } + strcpy((char*)uncompr, "garbage"); + + if (gzread(file, uncompr, (unsigned)uncomprLen) != len) { + fprintf(stderr, "gzread err: %s\n", gzerror(file, &err)); + exit(1); + } + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad gzread: %s\n", (char*)uncompr); + exit(1); + } else { + printf("gzread(): %s\n", (char*)uncompr); + } + + pos = gzseek(file, -8L, SEEK_CUR); + if (pos != 6 || gztell(file) != pos) { + fprintf(stderr, "gzseek error, pos=%ld, gztell=%ld\n", + (long)pos, (long)gztell(file)); + exit(1); + } + + if (gzgetc(file) != ' ') { + fprintf(stderr, "gzgetc error\n"); + exit(1); + } + + if (gzungetc(' ', file) != ' ') { + fprintf(stderr, "gzungetc error\n"); + exit(1); + } + + gzgets(file, (char*)uncompr, (int)uncomprLen); + if (strlen((char*)uncompr) != 7) { /* " hello!" */ + fprintf(stderr, "gzgets err after gzseek: %s\n", gzerror(file, &err)); + exit(1); + } + if (strcmp((char*)uncompr, hello + 6)) { + fprintf(stderr, "bad gzgets after gzseek\n"); + exit(1); + } else { + printf("gzgets() after gzseek: %s\n", (char*)uncompr); + } + + gzclose(file); +#endif +} + +#endif /* Z_SOLO */ + +/* =========================================================================== + * Test deflate() with small buffers + */ +static void test_deflate(Byte *compr, uLong comprLen) { + z_stream c_stream; /* compression stream */ + int err; + uLong len = (uLong)strlen(hello)+1; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_in = (z_const unsigned char *)hello; + c_stream.next_out = compr; + + while (c_stream.total_in != len && c_stream.total_out < comprLen) { + c_stream.avail_in = c_stream.avail_out = 1; /* force small buffers */ + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + } + /* Finish the stream, still forcing small buffers: */ + for (;;) { + c_stream.avail_out = 1; + err = deflate(&c_stream, Z_FINISH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "deflate"); + } + + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with small buffers + */ +static void test_inflate(Byte *compr, uLong comprLen, Byte *uncompr, + uLong uncomprLen) { + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = 0; + d_stream.next_out = uncompr; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + while (d_stream.total_out < uncomprLen && d_stream.total_in < comprLen) { + d_stream.avail_in = d_stream.avail_out = 1; /* force small buffers */ + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "inflate"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad inflate\n"); + exit(1); + } else { + printf("inflate(): %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Test deflate() with large buffers and dynamic change of compression level + */ +static void test_large_deflate(Byte *compr, uLong comprLen, Byte *uncompr, + uLong uncomprLen) { + z_stream c_stream; /* compression stream */ + int err; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_BEST_SPEED); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_out = compr; + c_stream.avail_out = (uInt)comprLen; + + /* At this point, uncompr is still mostly zeroes, so it should compress + * very well: + */ + c_stream.next_in = uncompr; + c_stream.avail_in = (uInt)uncomprLen; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + if (c_stream.avail_in != 0) { + fprintf(stderr, "deflate not greedy\n"); + exit(1); + } + + /* Feed in already compressed data and switch to no compression: */ + deflateParams(&c_stream, Z_NO_COMPRESSION, Z_DEFAULT_STRATEGY); + c_stream.next_in = compr; + c_stream.avail_in = (uInt)uncomprLen/2; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + + /* Switch back to compressing mode: */ + deflateParams(&c_stream, Z_BEST_COMPRESSION, Z_FILTERED); + c_stream.next_in = uncompr; + c_stream.avail_in = (uInt)uncomprLen; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + fprintf(stderr, "deflate should report Z_STREAM_END\n"); + exit(1); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with large buffers + */ +static void test_large_inflate(Byte *compr, uLong comprLen, Byte *uncompr, + uLong uncomprLen) { + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = (uInt)comprLen; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + for (;;) { + d_stream.next_out = uncompr; /* discard the output */ + d_stream.avail_out = (uInt)uncomprLen; + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "large inflate"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (d_stream.total_out != 2*uncomprLen + uncomprLen/2) { + fprintf(stderr, "bad large inflate: %ld\n", d_stream.total_out); + exit(1); + } else { + printf("large_inflate(): OK\n"); + } +} + +/* =========================================================================== + * Test deflate() with full flush + */ +static void test_flush(Byte *compr, uLong *comprLen) { + z_stream c_stream; /* compression stream */ + int err; + uInt len = (uInt)strlen(hello)+1; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_in = (z_const unsigned char *)hello; + c_stream.next_out = compr; + c_stream.avail_in = 3; + c_stream.avail_out = (uInt)*comprLen; + err = deflate(&c_stream, Z_FULL_FLUSH); + CHECK_ERR(err, "deflate"); + + compr[3]++; /* force an error in first compressed block */ + c_stream.avail_in = len - 3; + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + CHECK_ERR(err, "deflate"); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); + + *comprLen = c_stream.total_out; +} + +/* =========================================================================== + * Test inflateSync() + */ +static void test_sync(Byte *compr, uLong comprLen, Byte *uncompr, + uLong uncomprLen) { + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = 2; /* just read the zlib header */ + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + d_stream.next_out = uncompr; + d_stream.avail_out = (uInt)uncomprLen; + + err = inflate(&d_stream, Z_NO_FLUSH); + CHECK_ERR(err, "inflate"); + + d_stream.avail_in = (uInt)comprLen-2; /* read all compressed data */ + err = inflateSync(&d_stream); /* but skip the damaged part */ + CHECK_ERR(err, "inflateSync"); + + err = inflate(&d_stream, Z_FINISH); + if (err != Z_STREAM_END) { + fprintf(stderr, "inflate should report Z_STREAM_END\n"); + exit(1); + } + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + printf("after inflateSync(): hel%s\n", (char *)uncompr); +} + +/* =========================================================================== + * Test deflate() with preset dictionary + */ +static void test_dict_deflate(Byte *compr, uLong comprLen) { + z_stream c_stream; /* compression stream */ + int err; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_BEST_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + err = deflateSetDictionary(&c_stream, + (const Bytef*)dictionary, (int)sizeof(dictionary)); + CHECK_ERR(err, "deflateSetDictionary"); + + dictId = c_stream.adler; + c_stream.next_out = compr; + c_stream.avail_out = (uInt)comprLen; + + c_stream.next_in = (z_const unsigned char *)hello; + c_stream.avail_in = (uInt)strlen(hello)+1; + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + fprintf(stderr, "deflate should report Z_STREAM_END\n"); + exit(1); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with a preset dictionary + */ +static void test_dict_inflate(Byte *compr, uLong comprLen, Byte *uncompr, + uLong uncomprLen) { + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = (uInt)comprLen; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + d_stream.next_out = uncompr; + d_stream.avail_out = (uInt)uncomprLen; + + for (;;) { + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + if (err == Z_NEED_DICT) { + if (d_stream.adler != dictId) { + fprintf(stderr, "unexpected dictionary"); + exit(1); + } + err = inflateSetDictionary(&d_stream, (const Bytef*)dictionary, + (int)sizeof(dictionary)); + } + CHECK_ERR(err, "inflate with dict"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad inflate with dict\n"); + exit(1); + } else { + printf("inflate with dictionary: %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Usage: example [output.gz [input.gz]] + */ + +int main(int argc, char *argv[]) { + Byte *compr, *uncompr; + uLong uncomprLen = 20000; + uLong comprLen = 3 * uncomprLen; + static const char* myVersion = ZLIB_VERSION; + + if (zlibVersion()[0] != myVersion[0]) { + fprintf(stderr, "incompatible zlib version\n"); + exit(1); + + } else if (strcmp(zlibVersion(), ZLIB_VERSION) != 0) { + fprintf(stderr, "warning: different zlib version linked: %s\n", + zlibVersion()); + } + + printf("zlib version %s = 0x%04x, compile flags = 0x%lx\n", + ZLIB_VERSION, ZLIB_VERNUM, zlibCompileFlags()); + + compr = (Byte*)calloc((uInt)comprLen, 1); + uncompr = (Byte*)calloc((uInt)uncomprLen, 1); + /* compr and uncompr are cleared to avoid reading uninitialized + * data and to ensure that uncompr compresses well. + */ + if (compr == Z_NULL || uncompr == Z_NULL) { + printf("out of memory\n"); + exit(1); + } + +#ifdef Z_SOLO + (void)argc; + (void)argv; +#else + test_compress(compr, comprLen, uncompr, uncomprLen); + + test_gzio((argc > 1 ? argv[1] : TESTFILE), + uncompr, uncomprLen); +#endif + + test_deflate(compr, comprLen); + test_inflate(compr, comprLen, uncompr, uncomprLen); + + test_large_deflate(compr, comprLen, uncompr, uncomprLen); + test_large_inflate(compr, comprLen, uncompr, uncomprLen); + + test_flush(compr, &comprLen); + test_sync(compr, comprLen, uncompr, uncomprLen); + comprLen = 3 * uncomprLen; + + test_dict_deflate(compr, comprLen); + test_dict_inflate(compr, comprLen, uncompr, uncomprLen); + + free(compr); + free(uncompr); + + return 0; +} diff --git a/esy-test/package.json b/esy-test/package.json new file mode 100644 index 0000000..650ee02 --- /dev/null +++ b/esy-test/package.json @@ -0,0 +1,11 @@ +{ + "esy": { + "build": [ + "#{os == 'windows' ? 'x86_64-w64-mingw32-':''}gcc example.c -I#{esy-zlib.install / 'include'} -L#{esy-zlib.lib} -lz -o test", + "./test" + ] + }, + "dependencies": { + "esy-zlib": "*" + } +} diff --git a/esy.json b/esy.json new file mode 100644 index 0000000..a39632d --- /dev/null +++ b/esy.json @@ -0,0 +1,42 @@ +{ + "name": "esy-zlib", + "version": "1.3.1000", + "description": "zlib packaged for esy", + "source": "https://zlib.net/zlib-1.3.1.tar.gz#sha256:9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23", + "override": { + "exportedEnv": { + "CFLAGS": { + "scope": "global", + "val": "-I#{self.install / 'include'} $CFLAGS" + }, + "LDFLAGS": { + "scope": "global", + "val": "-L#{self.lib} -lz $LDFLAGS" + }, + "PKG_CONFIG_PATH": { + "scope": "global", + "val": "#{self.lib / 'pkgconfig' : $PKG_CONFIG_PATH }" + }, + "Z_LIB_PATH": { + "val": "#{self.lib}", + "scope": "global" + } + }, + "buildEnv": { + "CHOST": "#{os == 'windows' ? 'x86_64-w64-mingw32': '' }", + "CFLAGS": "-fPIC -O2" + }, + "build": [ + [ + "bash", + "-c", + "./configure --prefix=$cur__install #{os == 'windows' ? '--uname=cygwin' : ''}" + ], + "make", + "make test" + ], + "install": "make install", + "buildsInSource": true, + "dependencies": {} + } +}