Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update PackageGraph class, add tests #21

Merged
merged 2 commits into from
Feb 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ build/
.pub/
pubspec.lock

generated
# Include .packages files from tests which are hand coded
!test/package_graph/**/.packages
93 changes: 51 additions & 42 deletions lib/src/package_graph/package_graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,98 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';

import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';

/// A graph of the package dependencies for an application.
class PackageGraph {
/// The root application package.
final PackageNode root;

/// All the package dependencies of [root], transitive and direct.
Set<PackageNode> get allPackages {
var seen = new Set<PackageNode>()..add(root);
/// All [PackageNodes] indexed by package name.
final Map<String, PackageNode> allPackages;

void addDeps(PackageNode package) {
for (var dep in package.dependencies) {
if (seen.contains(dep)) continue;
seen.add(dep);
addDeps(dep);
}
}
addDeps(root);

return seen;
}
PackageGraph._(this.root, Map<String, PackageNode> allPackages)
: allPackages = new Map.unmodifiable(allPackages);

PackageGraph._(this.root);

/// Creates a [PackageGraph] for the package in which you are currently
/// running.
factory PackageGraph.forThisPackage() {
/// Creates a [PackageGraph] for the package whose top level directory lives
/// at [packagePath] (no trailing slash).
factory PackageGraph.forPath(String packagePath) {
/// Read in the pubspec file and parse it as yaml.
var pubspec = new File('pubspec.yaml');
var pubspec = new File(path.join(packagePath, 'pubspec.yaml'));
if (!pubspec.existsSync()) {
throw 'Unable to generate package graph, no `pubspec.yaml` found.';
throw 'Unable to generate package graph, no `pubspec.yaml` found. '
'This program must be ran from the root directory of your package.';
}
var yaml = loadYaml(pubspec.readAsStringSync());
var rootYaml = loadYaml(pubspec.readAsStringSync());

/// Read in the `.packages` file to get the locations of all packages.
var packagesFile = new File('.packages');
var packagesFile = new File(path.join(packagePath, '.packages'));
if (!packagesFile.existsSync()) {
throw 'Unable to generate package graph, no `.packages` found.';
throw 'Unable to generate package graph, no `.packages` found. '
'This program must be ran from the root directory of your package.';
}
var packageLocations = <String, Uri>{};
packagesFile.readAsLinesSync().skip(1).forEach((line) {
var firstColon = line.indexOf(':');
var name = line.substring(0, firstColon);
var uriString = line.substring(firstColon + 1);
assert(line.endsWith('lib${Platform.pathSeparator}'));
// Start after package_name:, and strip out trailing `lib` dir.
var uriString = line.substring(firstColon + 1, line.length - 4);
var uri;
try {
packageLocations[name] = Uri.parse(uriString);
uri = Uri.parse(uriString);
} on FormatException catch (_) {
/// Some types of deps don't have a scheme, and just point to a relative
/// path.
packageLocations[name] = new Uri.file(uriString);
uri = new Uri.file(uriString);
}
if (!uri.isAbsolute) {
uri = new Uri.file(path.join(packagePath, uri.path));
}
packageLocations[name] = uri;
});

/// Create all [PackageNode]s for all deps.
var nodes = <String, PackageNode>{};
Map<String, dynamic> rootDeps;
PackageNode addNodeAndDeps(YamlMap yaml, PackageDependencyType type,
{bool isRoot: false}) {
var name = yaml['name'];
assert(!nodes.containsKey(name));
var node = new PackageNode._(name, yaml['version'], type);
var node = new PackageNode._(
name, yaml['version'], type, packageLocations[name]);
nodes[name] = node;

_depsFromYaml(yaml, withOverrides: isRoot).forEach((name, source) {
var deps = _depsFromYaml(yaml, withOverrides: isRoot);
if (isRoot) rootDeps = deps;
deps.forEach((name, source) {
var dep = nodes[name];
if (dep == null) {
var pubspec = _pubspecForUri(packageLocations[name]);
dep = addNodeAndDeps(pubspec, _dependencyType(source));
dep = addNodeAndDeps(pubspec, _dependencyType(rootDeps[name]));
}
node._dependencies.add(dep);
});

return node;
}

var root = addNodeAndDeps(yaml, PackageDependencyType.Path, isRoot: true);
return new PackageGraph._(root);
var root =
addNodeAndDeps(rootYaml, PackageDependencyType.Path, isRoot: true);
return new PackageGraph._(root, nodes);
}

/// Creates a [PackageGraph] for the package in which you are currently
/// running.
factory PackageGraph.forThisPackage() => new PackageGraph.forPath('.');

/// Shorthand to get a package by name.
PackageNode operator [](String packageName) => allPackages[packageName];

String toString() {
var buffer = new StringBuffer();
for (var package in allPackages) {
for (var package in allPackages.values) {
buffer.writeln('$package');
}
return buffer.toString();
Expand All @@ -106,12 +116,16 @@ class PackageNode {
final List<PackageNode> _dependencies = [];
Iterable<PackageNode> get dependencies => _dependencies.toList();

PackageNode._(this.name, this.version, this.dependencyType);
/// The location of the current version of this package.
final Uri location;

PackageNode._(this.name, this.version, this.dependencyType, this.location);

String toString() => '''
$name:
version: $version
type: $dependencyType
location: $location
dependencies: [${dependencies.map((d) => d.name).join(', ')}]''';
}

Expand All @@ -120,7 +134,7 @@ class PackageNode {
enum PackageDependencyType { Pub, Github, Path, }

PackageDependencyType _dependencyType(source) {
if (source is String) return PackageDependencyType.Pub;
if (source is String || source == null) return PackageDependencyType.Pub;

assert(source is YamlMap);
assert(source.keys.length == 1);
Expand All @@ -147,14 +161,9 @@ Map<String, YamlMap> _depsFromYaml(YamlMap yaml, {bool withOverrides: false}) {
return deps;
}

/// [uri] should be directly from a `.packages` file, and points to the `lib`
/// dir.
/// [uri] should point to the top level directory for the package.
YamlMap _pubspecForUri(Uri uri) {
var libPath = uri.toFilePath();
assert(libPath.endsWith('lib/'));
var pubspecPath =
libPath.replaceRange(libPath.length - 4, libPath.length, 'pubspec.yaml');

var pubspecPath = path.join(uri.toFilePath(), 'pubspec.yaml');
var pubspec = new File(pubspecPath);
if (!pubspec.existsSync()) {
throw 'Unable to generate package graph, no `$pubspecPath` found.';
Expand Down
6 changes: 6 additions & 0 deletions test/package_graph/basic_pkg/.packages
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Generated by hand on 2016-02-01
a:pkg/a/lib/
b:pkg/b/lib/
c:pkg/c/lib/
d:pkg/d/lib/
basic_pkg:lib/
5 changes: 5 additions & 0 deletions test/package_graph/basic_pkg/pkg/a/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: a
version: 2.0.0
dependencies:
b: ^3.0.0
c: any
4 changes: 4 additions & 0 deletions test/package_graph/basic_pkg/pkg/b/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name: b
version: 3.0.0
dependencies:
c: ^0.4.0
4 changes: 4 additions & 0 deletions test/package_graph/basic_pkg/pkg/c/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name: c
version: 4.0.0
dependencies:
basic_pkg: ^1.0.0
8 changes: 8 additions & 0 deletions test/package_graph/basic_pkg/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: basic_pkg
version: 1.0.0
dependencies:
a: 2.0.0
b:
git: git://github.com/b/b.git
c:
path: pkg/c
2 changes: 2 additions & 0 deletions test/package_graph/no_packages_file/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name: a
version: 1.0.0
1 change: 1 addition & 0 deletions test/package_graph/no_pubspec/.packages
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Created by hand, but empty
78 changes: 78 additions & 0 deletions test/package_graph/package_graph_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@TestOn('vm')
import 'package:test/test.dart';

import 'package:build/build.dart';

main() async {
PackageGraph graph;

group('forThisPackage ', () {
setUp(() async {
graph = await new PackageGraph.forThisPackage();
});

test('root', () {
expectPkg(
graph.root, 'build', isNotEmpty, PackageDependencyType.Path, './');
});
});

group('basic package ', () {
var basicPkgPath = 'test/package_graph/basic_pkg';

setUp(() async {
graph = await new PackageGraph.forPath(basicPkgPath);
});

test('allPackages', () {
expect(graph.allPackages, {
'a': graph['a'],
'b': graph['b'],
'c': graph['c'],
'basic_pkg': graph['basic_pkg'],
});
});

test('root', () {
expectPkg(graph.root, 'basic_pkg', '1.0.0', PackageDependencyType.Path,
basicPkgPath, [graph['a'], graph['b'], graph['c']]);
});

test('pub dependency', () {
expectPkg(graph['a'], 'a', '2.0.0', PackageDependencyType.Pub,
'$basicPkgPath/pkg/a/', [graph['b'], graph['c']]);
});

test('git dependency', () {
expectPkg(graph['b'], 'b', '3.0.0', PackageDependencyType.Github,
'$basicPkgPath/pkg/b/', [graph['c']]);
});

test('path dependency', () {
expectPkg(graph['c'], 'c', '4.0.0', PackageDependencyType.Path,
'$basicPkgPath/pkg/c/', [graph['basic_pkg']]);
});
});

test('missing pubspec throws on create', () {
expect(() => new PackageGraph.forPath('no_pubspec'), throws);
});

test('missing .packages file throws on create', () {
expect(() => new PackageGraph.forPath('no_packages_file'), throws);
});
}

void expectPkg(PackageNode node, name, version, type, location,
[Iterable<PackageNode> dependencies]) {
expect(node.name, name);
expect(node.version, version);
expect(node.dependencyType, type);
expect(node.location.path, location);
if (dependencies != null) {
expect(node.dependencies, unorderedEquals(dependencies));
}
}