Skip to content

Commit

Permalink
Leverage elm-json solve (updated) (#451)
Browse files Browse the repository at this point in the history
Fixes #328, fixes #277, fixes #273, fixes #323, fixes #374

zwilias  recently released [`elm-json`](https://github.com/zwilias/elm-json), a tool for interacting with `elm.json` files. One of the available subcommands is `solve`: Given an elm.json file (for a package or application), it tries to generate a single set of direct and indirect dependencies that satisfy all constraints. It has 2 flags that make it useful for `node-test-runner`:
- `--test` => Includes the `test-dependencies` and promotes them to direct dependencies
- `--extra` => Allows providing an extra set of packages that need to be direct dependencies of the final result

Since `elm-json` can be installed through `npm` and has binaries available for OS X, Windows and Linux (statically linked), it seems like a fine candidate to use in `node-test-runner` for calculating the final set of dependencies needed for the generated application.
  • Loading branch information
lydell authored Oct 14, 2020
1 parent cc6003d commit 9c60f90
Show file tree
Hide file tree
Showing 10 changed files with 46 additions and 240 deletions.
110 changes: 3 additions & 107 deletions lib/Generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const path = require('path'),
fs = require('fs-extra'),
Murmur = require('murmur-hash-js'),
Version = require('./version.js'),
Solve = require('./solve.js'),
Compile = require('./Compile.js'),
supportsColor = require('chalk').supportsColor;

Expand Down Expand Up @@ -51,10 +51,7 @@ function readUtf8(filepath) {

function generateElmJson(
projectRootDir /*: string */,
pathToElmBinary /*: string */,
generatedCodeDir /*: string */,
filePaths /*: Array<string> */,
packageIndirectDeps /*: Object */,
hasBeenGivenCustomGlobs /*: boolean */
) /*: [string, string, Array<string>] */ {
const testRootDir = Compile.getTestRootDir(projectRootDir);
Expand All @@ -70,6 +67,7 @@ function generateElmJson(
process.exit(1);
}
var isPackageProject = projectElmJson.type === 'package';
var dependencies = Solve.get_dependencies(elmJsonPath);

// if we were given file globs, we don't need to check the tests/ directory exists
// this is only for elm applications, which people may need to introduce slowly into their apps
Expand Down Expand Up @@ -112,34 +110,13 @@ function generateElmJson(
type: 'application',
'source-directories': [], // these are added below
'elm-version': '0.19.1',
dependencies: {
direct: {},
indirect: {},
},
dependencies: dependencies,
'test-dependencies': {
direct: {},
indirect: {},
},
};

var nodeTestRunnerElmJsonPath = path.resolve(
path.join(__dirname, '..', 'elm', 'elm.json')
);
var nodeTestRunnerElmJson = fs.readJsonSync(nodeTestRunnerElmJsonPath);
// we want to use the version of elm-explorations/test that the user
// specifies in their own test-dependencies. everything else needs to
// be included for the test runner to compile.
delete nodeTestRunnerElmJson.dependencies.direct['elm-explorations/test'];
addDirectDependencies(
nodeTestRunnerElmJson['dependencies']['direct'],
isPackageProject,
testElmJson
);
addIndirectDependencies(
nodeTestRunnerElmJson['dependencies']['indirect'],
testElmJson
);

// Make all the source-directories absolute, and introduce a new one.
var projectSourceDirs;
if (isPackageProject) {
Expand Down Expand Up @@ -182,39 +159,6 @@ function generateElmJson(
return path.relative(generatedCodeDir, absolutePath);
});

if (isPackageProject) {
addDirectDependencies(
projectElmJson['dependencies'],
isPackageProject,
testElmJson
);
addDirectDependencies(
projectElmJson['test-dependencies'],
isPackageProject,
testElmJson
);
addIndirectDependencies(packageIndirectDeps, testElmJson);
} else {
addDirectDependencies(
projectElmJson['dependencies']['direct'],
isPackageProject,
testElmJson
);
addIndirectDependencies(
projectElmJson['dependencies']['indirect'],
testElmJson
);
addDirectDependencies(
projectElmJson['test-dependencies']['direct'],
isPackageProject,
testElmJson
);
addIndirectDependencies(
projectElmJson['test-dependencies']['indirect'],
testElmJson
);
}

// Generate the new elm.json, if necessary.
const generatedContents = JSON.stringify(testElmJson, null, 4);
const generatedPath = path.join(generatedCodeDir, 'elm.json');
Expand All @@ -235,54 +179,6 @@ function generateElmJson(
return [generatedCodeDir, generatedSrc, sourceDirs];
}

function addDirectDependencies(
deps /*: Object */,
isPackageProject /*: boolean */,
testElmJson /*: Object */
) {
Object.keys(deps).forEach(function (name) {
var version = deps[name];
if (isPackageProject) {
// Use the lowest version in the range.
// NOTE: technically this doesn't work if someone does something weird like:
//
// "2.0.0 < v < 3.0.0"
//
// ...but we're choosing not to support that right now.
version = version.split(' ')[0];
}
if (testElmJson['dependencies']['direct'].hasOwnProperty(name)) {
var existingVersion = testElmJson['dependencies']['direct'][name];

// If we have a clash, choose the higher of the two versions.
// This may not work! It's entirely possible that the result won't
// compile. We're going to try it and see what happens.
version = Version.getHigherVersion(version, existingVersion);
}
testElmJson['dependencies']['direct'][name] = version;
});
}

function addIndirectDependencies(deps, testElmJson /*: Object */) {
Object.keys(deps).forEach(function (name) {
if (testElmJson['dependencies']['direct'].hasOwnProperty(name)) {
// already a normal dep
} else {
var version = deps[name];

if (testElmJson['dependencies']['indirect'].hasOwnProperty(name)) {
var existingVersion = testElmJson['dependencies']['indirect'][name];

// If we have a clash, choose the higher of the two versions.
// This may not work! It's entirely possible that the result won't
// compile. We're going to try it and see what happens.
version = Version.getHigherVersion(version, existingVersion);
}

testElmJson['dependencies']['indirect'][name] = version;
}
});
}
function generateMainModule(
fuzz /*: number */,
seed /*: number */,
Expand Down
42 changes: 0 additions & 42 deletions lib/Runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,47 +30,6 @@ function getFirstLine(pathToFile /*: string */) /*: Promise<string> */ {

var readElmiPath = require('elmi-to-json').paths['elmi-to-json'];

function getIndirectDeps(projectRootDir /*: string */) /*: Promise<Object> */ {
return new Promise(function (resolve, reject) {
var proc = spawn(readElmiPath, ['--for-elm-test'], {
cwd: projectRootDir,
env: process.env,
});
let jsonStr = '';
let stderrStr = '';

proc.stdout.on('data', function (data) {
jsonStr += data;
});

proc.stderr.on('data', function (data) {
stderrStr += data;
});

proc.on('close', function (code) {
if (stderrStr !== '') {
reject(stderrStr);
} else if (code !== 0) {
reject('Finding package interface failed, exiting with code ' + code);
}

try {
let outline = JSON.parse(jsonStr).outline;

if (outline.type !== 'ValidPkg') {
reject(
'Invalid package - please run `elm make` instead of `elm test` and fix the errors you see!'
);
} else {
resolve(outline.exactDeps);
}
} catch (err) {
reject('Received invalid JSON from package interface search: ' + err);
}
});
});
}

function moduleFromFilePath(filePathArg) {
var parsed = path.parse(path.normalize(filePathArg));
var basename = path.basename(parsed.base, '.elm');
Expand Down Expand Up @@ -198,6 +157,5 @@ function verifyModules(filePaths) {

module.exports = {
findTests: findTests,
getIndirectDeps: getIndirectDeps,
getFirstLine: getFirstLine,
};
44 changes: 11 additions & 33 deletions lib/elm-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ var fs = require('fs-extra'),
path = require('path'),
minimist = require('minimist'),
chokidar = require('chokidar'),
Runner = require('./Runner.js'),
Supervisor = require('./Supervisor.js');

// Check Node version
Expand Down Expand Up @@ -338,17 +337,14 @@ if (args._[0] === 'make') {
pathToElmBinary,
args.report
)
.then(() => {
Runner.getIndirectDeps(projectRootDir).then((packageIndirectDeps) => {
return elmTestMake(
projectRootDir,
pathToElmBinary,
testFilePaths,
packageIndirectDeps,
hasBeenGivenCustomGlobs
);
});
})
.then(() =>
elmTestMake(
projectRootDir,
pathToElmBinary,
testFilePaths,
hasBeenGivenCustomGlobs
)
)
.catch((err) => {
console.error(err.message);
process.exit(1);
Expand All @@ -358,7 +354,6 @@ if (args._[0] === 'make') {
projectRootDir,
pathToElmBinary,
testFilePaths,
{},
hasBeenGivenCustomGlobs
);
}
Expand Down Expand Up @@ -406,40 +401,27 @@ if (args._[0] === 'make') {
pathToElmBinary,
args.report
)
.then(() => {
Runner.getIndirectDeps(projectRootDir).then((packageIndirectDeps) => {
return generateAndRun(
projectRootDir,
testFileGlobs,
testFilePaths,
packageIndirectDeps
);
});
})
.then(() => generateAndRun(projectRootDir, testFileGlobs, testFilePaths))
.catch((err) => {
console.error(err.message);
process.exit(1);
});
} else {
generateAndRun(projectRootDir, testFileGlobs, testFilePaths, {});
generateAndRun(projectRootDir, testFileGlobs, testFilePaths);
}
}

function generateAndRun(
projectRootDir /*: string */,
testFileGlobs /* Array<string> */,
testFilePaths /*: Array<string> */,
packageIndirectDeps /*: Object */
testFilePaths /*: Array<string> */
) {
const generatedCodeDir = Compile.getGeneratedCodeDir(projectRootDir);
const hasBeenGivenCustomGlobs = testFileGlobs.length > 0;

const returnValues = Generate.generateElmJson(
projectRootDir,
pathToElmBinary,
generatedCodeDir,
testFilePaths,
packageIndirectDeps,
hasBeenGivenCustomGlobs
);
const generatedSrc = returnValues[1];
Expand Down Expand Up @@ -528,17 +510,13 @@ function elmTestMake(
projectRootDir /*: string */,
pathToElmBinary /*: string */,
testFilePaths /*: Array<string> */,
packageIndirectDeps /*: Object */,
hasBeenGivenCustomGlobs /*: boolean */
) {
const generatedCodeDir = Compile.getGeneratedCodeDir(projectRootDir);

Generate.generateElmJson(
projectRootDir,
pathToElmBinary,
generatedCodeDir,
testFilePaths,
packageIndirectDeps,
hasBeenGivenCustomGlobs
);

Expand Down
31 changes: 31 additions & 0 deletions lib/solve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const spawn = require('cross-spawn');

function get_dependencies(pathToElmJson) {
var result = spawn.sync(
'elm-json',
[
'solve',
'--test',
'--extra',
'elm/core',
'elm/json',
'elm/time',
'elm/random',
'--',
pathToElmJson,
],
{
silent: true,
env: process.env,
}
);

if (result.status != 0) {
console.error(result.stderr.toString());
process.exit(1);
}

return JSON.parse(result.stdout.toString());
}

module.exports = { get_dependencies };
33 changes: 0 additions & 33 deletions lib/version.js

This file was deleted.

1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9c60f90

Please sign in to comment.