blob: 8f6ea77564c2771e59a27500c670b3548c73776c [file] [log] [blame]
// Copyright (c) 2012, 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.
library builtin;
import 'dart:io';
import 'dart:async';
import 'dart:convert';
// import 'root_library'; happens here from C Code
// The root library (aka the script) is imported into this library. The
// standalone embedder uses this to lookup the main entrypoint in the
// root library's namespace.
Function _getMainClosure() => main;
// Corelib 'print' implementation.
void _print(arg) {
_Logger._printString(arg.toString());
}
class _Logger {
static void _printString(String s) native "Logger_PrintString";
}
_getPrintClosure() => _print;
void _logResolution(String msg) {
final enabled = false;
if (enabled) {
_Logger._printString(msg);
}
}
// Corelib 'Uri.base' implementation.
Uri _uriBase() {
return new Uri.file(Directory.current.path + "/");
}
_getUriBaseClosure() => _uriBase;
var _httpRequestResponseCode = 0;
var _httpRequestStatusString;
var _httpRequestResponse;
_getHttpRequestResponseCode() => _httpRequestResponseCode;
_getHttpRequestStatusString() => _httpRequestStatusString;
_getHttpRequestResponse() => _httpRequestResponse;
void _requestCompleted(List<int> data, HttpClientResponse response) {
_httpRequestResponseCode = response.statusCode;
_httpRequestStatusString = '${response.statusCode} ${response.reasonPhrase}';
_httpRequestResponse = null;
if (response.statusCode != 200 ||
(response.headers.contentType != null &&
response.headers.contentType.mimeType == 'application/json')) {
return;
}
_httpRequestResponse = data;
}
void _requestFailed(error) {
_httpRequestResponseCode = 0;
_httpRequestStatusString = error.toString();
_httpRequestResponse = null;
}
void _makeHttpRequest(String uri) {
var _client = new HttpClient();
_httpRequestResponseCode = 0;
_httpRequestStatusString = null;
_httpRequestResponse = null;
try {
Uri requestUri = Uri.parse(uri);
_client.getUrl(requestUri)
.then((HttpClientRequest request) {
request.persistentConnection = false;
return request.close();
})
.then((HttpClientResponse response) {
// Only create a ByteBuilder, if multiple chunks are received.
var builder = new BytesBuilder(copy: false);
response.listen(
builder.add,
onDone: () {
_requestCompleted(builder.takeBytes(), response);
// Close the client to stop any timers currently held alive.
_client.close();
},
onError: _requestFailed);
}).catchError((error) {
_requestFailed(error);
});
} catch (error) {
_requestFailed(error);
}
// TODO(floitsch): remove this line. It's just here to push an event on the
// event loop so that we invoke the scheduled microtasks. Also remove the
// import of dart:async when this line is not needed anymore.
Timer.run(() {});
}
void _httpGet(Uri uri, loadCallback(List<int> data)) {
var httpClient = new HttpClient();
try {
httpClient.getUrl(uri)
.then((HttpClientRequest request) {
request.persistentConnection = false;
return request.close();
})
.then((HttpClientResponse response) {
// Only create a ByteBuilder if multiple chunks are received.
var builder = new BytesBuilder(copy: false);
response.listen(
builder.add,
onDone: () {
if (response.statusCode != 200) {
var msg = 'Failure getting $uri: '
'${response.statusCode} ${response.reasonPhrase}';
_asyncLoadError(uri.toString(), msg);
}
List<int> data = builder.takeBytes();
httpClient.close();
loadCallback(data);
},
onError: (error) {
_asyncLoadError(uri.toString(), error);
});
})
.catchError((error) {
_asyncLoadError(uri.toString(), error);
});
} catch (error) {
_asyncLoadError(uri.toString(), error);
}
// TODO(floitsch): remove this line. It's just here to push an event on the
// event loop so that we invoke the scheduled microtasks. Also remove the
// import of dart:async when this line is not needed anymore.
Timer.run(() {});
}
// Are we running on Windows?
var _isWindows = false;
var _workingWindowsDrivePrefix;
// The current working directory
var _workingDirectoryUri;
// The URI that the entry point script was loaded from. Remembered so that
// package imports can be resolved relative to it.
var _entryPointScript;
// The directory to look in to resolve "package:" scheme URIs.
var _packageRoot;
void _setWindows() {
_isWindows = true;
}
_sanitizeWindowsPath(path) {
// For Windows we need to massage the paths a bit according to
// http://blogs.msdn.com/b/ie/archive/2006/12/06/file-uris-in-windows.aspx
//
// Convert
// C:\one\two\three
// to
// /C:/one/two/three
if (_isWindows == false) {
// Do nothing when not running Windows.
return path;
}
var fixedPath = "${path.replaceAll('\\', '/')}";
if ((path.length > 2) && (path[1] == ':')) {
// Path begins with a drive letter.
return '/$fixedPath';
}
return fixedPath;
}
_enforceTrailingSlash(uri) {
// Ensure we have a trailing slash character.
if (!uri.endsWith('/')) {
return '$uri/';
}
return uri;
}
_extractDriveLetterPrefix(cwd) {
if (!_isWindows) {
return null;
}
if (cwd.length > 1 && cwd[1] == ':') {
return '/${cwd[0]}:';
}
return null;
}
void _setWorkingDirectory(cwd) {
_workingWindowsDrivePrefix = _extractDriveLetterPrefix(cwd);
cwd = _sanitizeWindowsPath(cwd);
cwd = _enforceTrailingSlash(cwd);
_workingDirectoryUri = new Uri(scheme: 'file', path: cwd);
_logResolution('# Working Directory: $cwd');
}
_setPackageRoot(String packageRoot) {
packageRoot = _enforceTrailingSlash(packageRoot);
if (packageRoot.startsWith('file:') ||
packageRoot.startsWith('http:') ||
packageRoot.startsWith('https:')) {
_packageRoot = _workingDirectoryUri.resolve(packageRoot);
} else {
_packageRoot = _workingDirectoryUri.resolveUri(new Uri.file(packageRoot));
}
_logResolution('# Package root: $packageRoot -> $_packageRoot');
}
String _resolveScriptUri(String scriptName) {
if (_workingDirectoryUri == null) {
throw 'No current working directory set.';
}
scriptName = _sanitizeWindowsPath(scriptName);
var scriptUri = Uri.parse(scriptName);
if (scriptUri.scheme != '') {
// Script has a scheme, assume that it is fully formed.
_entryPointScript = scriptUri;
} else {
// Script does not have a scheme, assume that it is a path,
// resolve it against the working directory.
_entryPointScript = _workingDirectoryUri.resolve(scriptName);
}
_logResolution('# Resolved entry point to: $_entryPointScript');
return _entryPointScript.toString();
}
const _DART_EXT = 'dart-ext:';
String _resolveUri(String base, String userString) {
_logResolution('# Resolving: $userString from $base');
var baseUri = Uri.parse(base);
if (userString.startsWith(_DART_EXT)) {
var uri = userString.substring(_DART_EXT.length);
return '$_DART_EXT${baseUri.resolve(uri)}';
} else {
return '${baseUri.resolve(userString)}';
}
}
// Returns either a file path or a URI starting with http:, as a String.
String _filePathFromUri(String userUri) {
var uri = Uri.parse(userUri);
_logResolution('# Getting file path from: $uri');
var path;
switch (uri.scheme) {
case '':
case 'file':
return uri.toFilePath();
break;
case 'package':
return _filePathFromPackageUri(uri);
break;
case 'http':
return uri.toString();
default:
// Only handling file, http, and package URIs
// in standalone binary.
_logResolution('# Unknown scheme (${uri.scheme}) in $uri.');
throw 'Not a known scheme: $uri';
}
}
String _filePathFromPackageUri(Uri uri) {
if (!uri.host.isEmpty) {
var path = '${uri.host}${uri.path}';
var right = 'package:$path';
var wrong = 'package://$path';
throw "URIs using the 'package:' scheme should look like "
"'$right', not '$wrong'.";
}
var packageRoot = _packageRoot == null ?
_entryPointScript.resolve('packages/') :
_packageRoot;
return _filePathFromUri(packageRoot.resolve(uri.path).toString());
}
int _numOutstandingLoadRequests = 0;
void _signalDoneLoading() native "Builtin_DoneLoading";
void _loadScriptCallback(String uri, List<int> data) native "Builtin_LoadScript";
void _loadScript(String uri, List<int> data) {
// TODO: Currently a compilation error while loading the script is
// fatal for the isolate. _loadScriptCallback() does not return and
// the _numOutstandingLoadRequests counter remains out of sync.
_loadScriptCallback(uri, data);
assert(_numOutstandingLoadRequests > 0);
_numOutstandingLoadRequests--;
_logResolution("native Builtin_LoadScript($uri) completed, "
"${_numOutstandingLoadRequests} requests remaining");
if (_numOutstandingLoadRequests == 0) {
_signalDoneLoading();
}
}
void _asyncLoadErrorCallback(uri, error) native "Builtin_AsyncLoadError";
void _asyncLoadError(uri, error) {
assert(_numOutstandingLoadRequests > 0);
_numOutstandingLoadRequests--;
_asyncLoadErrorCallback(uri, error);
}
// Asynchronously loads script data (source or snapshot) through
// an http or file uri.
_loadDataAsync(String uri) {
uri = _resolveScriptUri(uri);
Uri sourceUri = Uri.parse(uri);
_numOutstandingLoadRequests++;
_logResolution("_loadDataAsync($uri), "
"${_numOutstandingLoadRequests} requests outstanding");
if (sourceUri.scheme == 'http') {
_httpGet(sourceUri, (data) {
_loadScript(uri, data);
});
} else {
var sourceFile = new File(_filePathFromUri(uri));
sourceFile.readAsBytes().then((data) {
_loadScript(uri, data);
},
onError: (e) {
_asyncLoadError(uri, e);
});
}
}
void _loadLibrarySourceCallback(tag, uri, libraryUri, text)
native "Builtin_LoadLibrarySource";
void _loadLibrarySource(tag, uri, libraryUri, text) {
// TODO: Currently a compilation error while loading the library is
// fatal for the isolate. _loadLibraryCallback() does not return and
// the _numOutstandingLoadRequests counter remains out of sync.
_loadLibrarySourceCallback(tag, uri, libraryUri, text);
assert(_numOutstandingLoadRequests > 0);
_numOutstandingLoadRequests--;
_logResolution("native Builtin_LoadLibrarySource($uri) completed, "
"${_numOutstandingLoadRequests} requests remaining");
if (_numOutstandingLoadRequests == 0) {
_signalDoneLoading();
}
}
// Asynchronously loads source code through an http or file uri.
_loadSourceAsync(int tag, String uri, String libraryUri) {
var filePath = _filePathFromUri(uri);
Uri sourceUri = Uri.parse(filePath);
_numOutstandingLoadRequests++;
_logResolution("_loadLibrarySource($uri), "
"${_numOutstandingLoadRequests} requests outstanding");
if (sourceUri.scheme == 'http') {
_httpGet(sourceUri, (data) {
var text = UTF8.decode(data);
_loadLibrarySource(tag, uri, libraryUri, text);
});
} else {
var sourceFile = new File(filePath);
sourceFile.readAsString().then((text) {
_loadLibrarySource(tag, uri, libraryUri, text);
},
onError: (e) {
_asyncLoadError(uri, e);
});
}
}
// Returns the directory part, the filename part, and the name
// of a native extension URL as a list [directory, filename, name].
// The directory part is either a file system path or an HTTP(S) URL.
// The filename part is the extension name, with the platform-dependent
// prefixes and extensions added.
_extensionPathFromUri(String userUri) {
if (!userUri.startsWith(_DART_EXT)) {
throw 'Unexpected internal error: Extension URI $userUri missing dart-ext:';
}
userUri = userUri.substring(_DART_EXT.length);
if (userUri.contains('\\')) {
throw 'Unexpected internal error: Extension URI $userUri contains \\';
}
String filename;
String name;
String path; // Will end in '/'.
int index = userUri.lastIndexOf('/');
if (index == -1) {
name = userUri;
path = './';
} else if (index == userUri.length - 1) {
throw 'Extension name missing in $extensionUri';
} else {
name = userUri.substring(index + 1);
path = userUri.substring(0, index + 1);
}
path = _filePathFromUri(path);
if (Platform.isLinux || Platform.isAndroid) {
filename = 'lib$name.so';
} else if (Platform.isMacOS) {
filename = 'lib$name.dylib';
} else if (Platform.isWindows) {
filename = '$name.dll';
} else {
_logResolution(
'Native extensions not supported on ${Platform.operatingSystem}');
throw 'Native extensions not supported on ${Platform.operatingSystem}';
}
return [path, filename, name];
}