2016-08-29 00:04:48 +02:00
// Copyright 2016 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
2016-10-15 00:48:34 +02:00
import ' dart:async ' ;
import ' dart:convert ' ;
2016-10-05 10:04:51 +02:00
import ' dart:io ' ;
2016-10-15 00:48:34 +02:00
import ' package:archive/archive.dart ' ;
2018-02-03 01:37:06 +01:00
import ' package:charcode/charcode.dart ' ;
2017-07-06 00:30:53 +02:00
import ' package:collection/collection.dart ' ;
2018-02-04 01:18:10 +01:00
import ' package:crypto/crypto.dart ' ;
2016-08-29 00:04:48 +02:00
import ' package:grinder/grinder.dart ' ;
2016-10-15 00:48:34 +02:00
import ' package:http/http.dart ' as http ;
2016-10-05 10:04:51 +02:00
import ' package:node_preamble/preamble.dart ' as preamble ;
2017-10-14 00:44:27 +02:00
import ' package:pub_semver/pub_semver.dart ' ;
2018-02-03 01:37:06 +01:00
import ' package:source_span/source_span.dart ' ;
2017-01-14 01:13:26 +01:00
import ' package:xml/xml.dart ' as xml ;
2016-10-08 00:46:58 +02:00
import ' package:yaml/yaml.dart ' ;
2016-08-29 00:04:48 +02:00
2017-07-07 04:29:39 +02:00
import ' package:sass/src/util/path.dart ' ;
2017-11-20 23:30:42 +01:00
import ' synchronize.dart ' ;
export ' synchronize.dart ' ;
2016-10-15 00:48:34 +02:00
/// The version of Dart Sass.
final String _version =
2017-01-08 23:31:26 +01:00
loadYaml ( new File ( ' pubspec.yaml ' ) . readAsStringSync ( ) ) [ ' version ' ] as String ;
2016-10-15 00:48:34 +02:00
/// The version of the current Dart executable.
2017-10-14 00:44:27 +02:00
final Version _dartVersion =
new Version . parse ( Platform . version . split ( " " ) . first ) ;
2016-10-15 00:48:34 +02:00
2017-10-12 21:22:01 +02:00
/// Whether we're using a dev Dart SDK.
2017-10-14 00:44:27 +02:00
bool get _isDevSdk = > _dartVersion . isPreRelease ;
2017-10-12 21:22:01 +02:00
2016-10-15 00:48:34 +02:00
/// The root of the Dart SDK.
final _sdkDir = p . dirname ( p . dirname ( Platform . resolvedExecutable ) ) ;
2018-02-02 03:54:42 +01:00
/// Whether we're using a 64-bit Dart SDK.
bool get _is64Bit = > Platform . version . contains ( " x64 " ) ;
2017-01-08 23:31:26 +01:00
main ( List < String > args ) = > grind ( args ) ;
2016-08-29 00:04:48 +02:00
2017-11-20 23:30:42 +01:00
@ DefaultTask ( ' Compile async code and reformat. ' )
2017-12-01 23:20:49 +01:00
@ Depends ( format , synchronize )
all ( ) { }
2017-11-20 23:30:42 +01:00
@ Task ( ' Run the Dart formatter. ' )
2016-08-29 00:04:48 +02:00
format ( ) {
Pub . run ( ' dart_style ' ,
script: ' format ' ,
arguments: [ ' --overwrite ' ]
. . addAll ( existingSourceDirs . map ( ( dir ) = > dir . path ) ) ) ;
}
2016-10-05 10:04:51 +02:00
2018-02-02 03:54:42 +01:00
@ Task ( ' Build Dart script snapshot. ' )
2016-10-05 10:04:51 +02:00
snapshot ( ) {
_ensureBuild ( ) ;
2017-06-05 21:52:56 +02:00
Dart . run ( ' bin/sass.dart ' , vmArgs: [ ' --snapshot=build/sass.dart.snapshot ' ] ) ;
2016-10-05 10:04:51 +02:00
}
2018-02-02 03:54:42 +01:00
@ Task ( ' Build Dart application snapshot. ' )
appSnapshot ( ) {
_ensureBuild ( ) ;
Dart . run ( ' bin/sass.dart ' ,
arguments: [ ' tool/app-snapshot-input.scss ' ] ,
vmArgs: [
' --snapshot=build/sass.dart.app.snapshot ' ,
' --snapshot-kind=app-jit '
] ,
quiet: true ) ;
}
2016-10-15 02:33:51 +02:00
@ Task ( ' Build standalone packages for all OSes. ' )
2018-02-02 03:54:42 +01:00
@ Depends ( snapshot , appSnapshot )
2016-10-15 00:48:34 +02:00
package ( ) async {
var client = new http . Client ( ) ;
2018-02-02 03:54:42 +01:00
await Future . wait ( [ " linux " , " macos " , " windows " ] . expand ( ( os ) = > [
_buildPackage ( client , os , x64: true ) ,
_buildPackage ( client , os , x64: false )
] ) ) ;
2016-10-15 00:48:34 +02:00
client . close ( ) ;
}
2016-10-05 10:04:51 +02:00
@ Task ( ' Compile to JS. ' )
js ( ) {
_ensureBuild ( ) ;
var destination = new File ( ' build/sass.dart.js ' ) ;
2017-07-07 00:03:27 +02:00
var args = [
2017-02-17 02:04:14 +01:00
' --trust-type-annotations ' ,
2016-10-15 02:38:50 +02:00
' -Dnode=true ' ,
2016-10-29 20:14:16 +02:00
' -Dversion= $ _version ' ,
' -Ddart-version= $ _dartVersion ' ,
2017-07-07 00:03:27 +02:00
] ;
if ( Platform . environment [ " SASS_MINIFY_JS " ] ! = " false " ) args . add ( " --minify " ) ;
2017-07-07 03:03:49 +02:00
Dart2js . compile ( new File ( ' bin/sass.dart ' ) ,
outFile: destination , extraArgs: args ) ;
2016-10-05 10:04:51 +02:00
var text = destination . readAsStringSync ( ) ;
2016-12-02 23:22:06 +01:00
destination . writeAsStringSync ( preamble . getPreamble ( ) + text ) ;
2016-10-15 02:33:51 +02:00
}
2016-10-25 01:45:51 +02:00
@ Task ( ' Build a pure-JS npm package. ' )
2016-10-15 02:33:51 +02:00
@ Depends ( js )
npm_package ( ) {
2017-06-07 00:24:22 +02:00
var json = JSON . decode ( new File ( ' package/package.json ' ) . readAsStringSync ( ) )
as Map < String , dynamic > ;
2017-06-07 00:11:12 +02:00
json [ ' version ' ] = _version ;
_writeNpmPackage ( ' build/npm ' , json ) ;
_writeNpmPackage ( ' build/npm-old ' , json . . addAll ( { " name " : " dart-sass " } ) ) ;
}
2017-12-01 23:20:49 +01:00
@ Task ( ' Installs dependencies from npm. ' )
npm_install ( ) = > run ( " npm " , arguments: [ " install " ] ) ;
@ Task ( ' Runs the tasks that are required for running tests. ' )
@ Depends ( npm_package , npm_install )
before_test ( ) { }
2017-06-07 00:11:12 +02:00
/// Writes a Dart Sass NPM package to the directory at [destination].
///
/// The [json] will be used as the package's package.json.
void _writeNpmPackage ( String destination , Map < String , dynamic > json ) {
var dir = new Directory ( destination ) ;
2016-10-31 21:25:05 +01:00
if ( dir . existsSync ( ) ) dir . deleteSync ( recursive: true ) ;
2016-10-15 02:33:51 +02:00
dir . createSync ( recursive: true ) ;
2017-06-07 00:11:12 +02:00
log ( " copying package/package.json to $ destination " ) ;
2018-02-03 01:37:06 +01:00
new File ( p . join ( destination , ' package.json ' ) )
2016-10-15 02:33:51 +02:00
. writeAsStringSync ( JSON . encode ( json ) ) ;
2017-02-11 00:24:56 +01:00
copy ( new File ( p . join ( ' package ' , ' sass.js ' ) ) , dir ) ;
copy ( new File ( p . join ( ' build ' , ' sass.dart.js ' ) ) , dir ) ;
2018-02-03 01:37:06 +01:00
log ( " copying package/README.npm.md to $ destination " ) ;
new File ( p . join ( destination , ' README.md ' ) )
. writeAsStringSync ( _readAndResolveMarkdown ( ' package/README.npm.md ' ) ) ;
}
final _readAndResolveRegExp = new RegExp (
r"^<!-- +#include +([^\s]+) +"
' "([^" \n ]+)" '
r" +-->$" ,
multiLine: true ) ;
/// Reads a Markdown file from [path] and resolves include directives.
///
/// Include directives have the syntax `"<!-- #include" PATH HEADER "-->"`,
/// which must appear on its own line. PATH is a relative file: URL to another
/// Markdown file, and HEADER is the name of a header in that file whose
/// contents should be included as-is.
String _readAndResolveMarkdown ( String path ) = > new File ( path )
. readAsStringSync ( )
. replaceAllMapped ( _readAndResolveRegExp , ( match ) {
String included ;
try {
included = new File ( p . join ( p . dirname ( path ) , p . fromUri ( match [ 1 ] ) ) )
. readAsStringSync ( ) ;
} catch ( error ) {
_matchError ( match , error . toString ( ) , url: p . toUri ( path ) ) ;
}
Match headerMatch ;
try {
headerMatch = " # ${ match [ 2 ] } \n " . allMatches ( included ) . first ;
} on StateError {
_matchError ( match , " Could not find header. " , url: p . toUri ( path ) ) ;
}
var headerLevel = 0 ;
var index = headerMatch . start ;
while ( index > = 0 & & included . codeUnitAt ( index ) = = $hash ) {
headerLevel + + ;
index - - ;
}
// The section goes until the next header of the same level, or the end
// of the document.
var sectionEnd = included . indexOf ( " # " * headerLevel , headerMatch . end ) ;
if ( sectionEnd = = - 1 ) sectionEnd = included . length ;
return included . substring ( headerMatch . end , sectionEnd ) . trim ( ) ;
} ) ;
/// Throws a nice [SourceSpanException] associated with [match].
void _matchError ( Match match , String message , { url } ) {
var file = new SourceFile . fromString ( match . input , url: url ) ;
throw new SourceSpanException ( message , file . span ( match . start , match . end ) ) ;
2016-10-05 10:04:51 +02:00
}
2017-01-14 01:13:26 +01:00
@ Task ( ' Build a Chocolatey package. ' )
@ Depends ( snapshot )
chocolatey_package ( ) {
_ensureBuild ( ) ;
var nuspec = _nuspec ( ) ;
var archive = new Archive ( )
. . addFile ( _fileFromString ( " sass.nuspec " , nuspec . toString ( ) ) )
. . addFile (
_file ( " [Content_Types].xml " , " package/chocolatey/[Content_Types].xml " ) )
. . addFile ( _file ( " _rels/.rels " , " package/chocolatey/rels.xml " ) )
. . addFile ( _fileFromString (
" package/services/metadata/core-properties/properties.psmdcp " ,
_nupkgProperties ( nuspec ) ) )
. . addFile ( _file ( " tools/LICENSE " , " LICENSE " ) )
. . addFile ( _file ( " tools/sass.dart.snapshot " , " build/sass.dart.snapshot " ) )
. . addFile ( _file ( " tools/chocolateyInstall.ps1 " ,
" package/chocolatey/chocolateyInstall.ps1 " ) )
. . addFile ( _file ( " tools/chocolateyUninstall.ps1 " ,
" package/chocolatey/chocolateyUninstall.ps1 " ) )
. . addFile ( _fileFromString ( " tools/sass.bat " ,
_readAndReplaceVersion ( " package/chocolatey/sass.bat " ) ) ) ;
var output = " build/sass. ${ _chocolateyVersion ( ) } .nupkg " ;
log ( " Creating $ output ... " ) ;
new File ( output ) . writeAsBytesSync ( new ZipEncoder ( ) . encode ( archive ) ) ;
}
/// Creates a `sass.nuspec` file's contents.
xml . XmlDocument _nuspec ( ) {
2017-10-14 00:44:27 +02:00
String sdkVersion ;
if ( _isDevSdk ) {
assert ( _dartVersion . preRelease [ 0 ] = = " dev " ) ;
assert ( _dartVersion . preRelease [ 1 ] is int ) ;
sdkVersion = " ${ _dartVersion . major } . ${ _dartVersion . minor } . "
" ${ _dartVersion . patch } . ${ _dartVersion . preRelease [ 1 ] } " ;
} else {
sdkVersion = _dartVersion . toString ( ) ;
}
2017-01-14 01:13:26 +01:00
var builder = new xml . XmlBuilder ( ) ;
builder . processing ( " xml " , ' version="1.0" ' ) ;
builder . element ( " package " , nest: ( ) {
builder
. namespace ( " http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd " ) ;
builder . element ( " metadata " , nest: ( ) {
builder . element ( " id " , nest: " sass " ) ;
builder . element ( " title " , nest: " Sass " ) ;
builder . element ( " version " , nest: _chocolateyVersion ( ) ) ;
builder . element ( " authors " , nest: " Natalie Weizenbaum " ) ;
builder . element ( " owners " , nest: " nex3 " ) ;
builder . element ( " projectUrl " , nest: " https://github.com/sass/dart-sass " ) ;
builder . element ( " licenseUrl " ,
nest: " https://github.com/sass/dart-sass/blob/ $ _version /LICENSE " ) ;
builder . element ( " iconUrl " ,
nest: " https://cdn.rawgit.com/sass/sass-site/ "
" f99ee33e4f688e244c7a5902c59d61f78daccc55/source/assets/img/ "
" logos/logo-seal.png " ) ;
builder . element ( " bugTrackerUrl " ,
nest: " https://github.com/sass/dart-sass/issues " ) ;
2017-07-06 23:13:29 +02:00
builder . element ( " description " , nest: """
2017-01-14 01:13:26 +01:00
* * Sass makes CSS fun again * * . Sass is an extension of CSS , adding nested rules , variables , mixins , selector inheritance , and more . It ' s translated to well-formatted, standard CSS using the command line tool or a web-framework plugin.
This package is Dart Sass , the new Dart implementation of Sass .
""" );
builder . element ( " summary " , nest: " Sass makes CSS fun again. " ) ;
builder . element ( " tags " , nest: " css preprocessor style sass " ) ;
builder . element ( " copyright " ,
nest: " Copyright ${ new DateTime . now ( ) . year } Google, Inc. " ) ;
builder . element ( " dependencies " , nest: ( ) {
builder . element ( " dependency " , attributes: {
2017-10-14 00:44:27 +02:00
" id " : _isDevSdk ? " dart-sdk-dev " : " dart-sdk " ,
2017-01-14 01:13:26 +01:00
// Unfortunately we need the exact same Dart version as we built with,
// since we ship a snapshot which isn't cross-version compatible. Once
// we switch to native compilation this won't be an issue.
2017-10-14 00:44:27 +02:00
" version " : " [ $ sdkVersion ] " ,
2017-01-14 01:13:26 +01:00
} ) ;
} ) ;
} ) ;
} ) ;
return builder . build ( ) as xml . XmlDocument ;
}
/// The current Sass version, formatted for Chocolatey which doesn't allow dots
/// in prerelease versions.
String _chocolateyVersion ( ) {
var components = _version . split ( " - " ) ;
if ( components . length = = 1 ) return components . first ;
assert ( components . length = = 2 ) ;
return " ${ components . first } - ${ components . last . replaceAll ( ' . ' , ' ' ) } " ;
}
/// Returns the contents of the `properties.psmdcp` file, computed from the
/// nuspec's XML.
String _nupkgProperties ( xml . XmlDocument nuspec ) {
var builder = new xml . XmlBuilder ( ) ;
builder . processing ( " xml " , ' version="1.0" ' ) ;
builder . element ( " coreProperties " , nest: ( ) {
builder . namespace (
" http://schemas.openxmlformats.org/package/2006/metadata/core-properties " ) ;
builder . namespace ( " http://purl.org/dc/elements/1.1/ " , " dc " ) ;
builder . element ( " dc:creator " ,
nest: nuspec . findAllElements ( " authors " ) . first . text ) ;
builder . element ( " dc:description " ,
nest: nuspec . findAllElements ( " description " ) . first . text ) ;
builder . element ( " dc:identifier " ,
nest: nuspec . findAllElements ( " id " ) . first . text ) ;
builder . element ( " version " ,
nest: nuspec . findAllElements ( " version " ) . first . text ) ;
builder . element ( " keywords " ,
nest: nuspec . findAllElements ( " tags " ) . first . text ) ;
builder . element ( " dc:title " ,
nest: nuspec . findAllElements ( " title " ) . first . text ) ;
} ) ;
return builder . build ( ) . toString ( ) ;
}
2016-10-05 10:04:51 +02:00
/// Ensure that the `build/` directory exists.
void _ensureBuild ( ) {
new Directory ( ' build ' ) . createSync ( recursive: true ) ;
}
2016-10-08 00:46:58 +02:00
2018-02-02 03:54:42 +01:00
/// Builds a standalone Sass package for the given [os] and architecture.
2016-10-15 00:48:34 +02:00
///
/// The [client] is used to download the corresponding Dart SDK.
2018-02-02 03:54:42 +01:00
Future _buildPackage ( http . Client client , String os , { bool x64: true } ) async {
var architecture = x64 ? " x64 " : " ia32 " ;
2017-12-02 21:54:04 +01:00
// TODO: Compile a single executable that embeds the Dart VM and the snapshot
// when dart-lang/sdk#27596 is fixed.
2017-10-12 21:22:01 +02:00
var channel = _isDevSdk ? " dev " : " stable " ;
var url = " https://storage.googleapis.com/dart-archive/channels/ $ channel / "
2016-10-15 00:48:34 +02:00
" release/ $ _dartVersion /sdk/dartsdk- $ os - $ architecture -release.zip " ;
log ( " Downloading $ url ... " ) ;
var response = await client . get ( Uri . parse ( url ) ) ;
if ( response . statusCode ~ / 100 ! = 2 ) {
throw " Failed to download package: ${ response . statusCode } "
" ${ response . reasonPhrase } . " ;
}
var dartExecutable = new ZipDecoder ( )
. decodeBytes ( response . bodyBytes )
. firstWhere ( ( file ) = > os = = ' windows '
? file . name . endsWith ( " /bin/dart.exe " )
: file . name . endsWith ( " /bin/dart " ) ) ;
2017-07-06 00:30:53 +02:00
var executable = DelegatingList . typed < int > ( dartExecutable . content as List ) ;
2018-02-02 03:54:42 +01:00
// Use the app snapshot when packaging for the current operating system.
//
// TODO: Use an app snapshot everywhere when dart-lang/sdk#28617 is fixed.
var snapshot = os = = Platform . operatingSystem & & x64 = = _is64Bit
? " build/sass.dart.app.snapshot "
: " build/sass.dart.snapshot " ;
2016-10-15 00:48:34 +02:00
var archive = new Archive ( )
2017-01-14 01:13:26 +01:00
. . addFile ( _fileFromBytes (
2016-11-02 21:01:47 +01:00
" dart-sass/src/dart ${ os = = ' windows ' ? ' .exe ' : ' ' } " , executable ,
executable: true ) )
2017-01-14 01:13:26 +01:00
. . addFile ( _file ( " dart-sass/src/DART_LICENSE " , p . join ( _sdkDir , ' LICENSE ' ) ) )
2018-02-02 03:54:42 +01:00
. . addFile ( _file ( " dart-sass/src/sass.dart.snapshot " , snapshot ) )
2017-01-14 01:13:26 +01:00
. . addFile ( _file ( " dart-sass/src/SASS_LICENSE " , " LICENSE " ) )
. . addFile ( _fileFromString (
" dart-sass/dart-sass ${ os = = ' windows ' ? ' .bat ' : ' ' } " ,
_readAndReplaceVersion (
" package/dart-sass. ${ os = = ' windows ' ? ' bat ' : ' sh ' } " ) ,
executable: true ) ) ;
2016-10-15 00:48:34 +02:00
var prefix = ' build/dart-sass- $ _version - $ os - $ architecture ' ;
if ( os = = ' windows ' ) {
var output = " $ prefix .zip " ;
log ( " Creating $ output ... " ) ;
new File ( output ) . writeAsBytesSync ( new ZipEncoder ( ) . encode ( archive ) ) ;
} else {
var output = " $ prefix .tar.gz " ;
log ( " Creating $ output ... " ) ;
new File ( output ) . writeAsBytesSync (
new GZipEncoder ( ) . encode ( new TarEncoder ( ) . encode ( archive ) ) ) ;
}
}
2017-01-14 01:13:26 +01:00
/// Reads [file], replaces all instances of SASS_VERSION with the actual
/// version, and returns its contents.
String _readAndReplaceVersion ( String file ) = >
new File ( file ) . readAsStringSync ( ) . replaceAll ( " SASS_VERSION " , _version ) ;
2016-10-15 00:48:34 +02:00
/// Creates an [ArchiveFile] with the given [path] and [data].
///
/// If [executable] is `true`, this marks the file as executable.
2017-01-14 01:13:26 +01:00
ArchiveFile _fileFromBytes ( String path , List < int > data ,
{ bool executable: false } ) = >
2016-10-15 00:48:34 +02:00
new ArchiveFile ( path , data . length , data )
. . mode = executable ? 495 : 428
. . lastModTime = new DateTime . now ( ) . millisecondsSinceEpoch ~ / 1000 ;
2017-01-14 01:13:26 +01:00
/// Creates a UTF-8-encoded [ArchiveFile] with the given [path] and [contents].
///
/// If [executable] is `true`, this marks the file as executable.
ArchiveFile _fileFromString ( String path , String contents ,
{ bool executable: false } ) = >
_fileFromBytes ( path , UTF8 . encode ( contents ) , executable: executable ) ;
/// Creates an [ArchiveFile] at the archive path [target] from the local file at
/// [source].
///
/// If [executable] is `true`, this marks the file as executable.
ArchiveFile _file ( String target , String source , { bool executable: false } ) = >
_fileFromBytes ( target , new File ( source ) . readAsBytesSync ( ) ,
executable: executable ) ;
2018-02-04 01:18:10 +01:00
/// A regular expression for locating the URL and SHA256 hash of the Sass
/// archive in the `homebrew-sass` formula.
final _homebrewRegExp = new RegExp ( r'\n( *)url "[^"]+"'
r'\n *sha256 "[^"]+"' ) ;
@ Task ( ' Update the Homebrew formula for the current version. ' )
update_homebrew ( ) async {
_ensureBuild ( ) ;
var process = await Process . start ( " git " , [
" archive " ,
" --prefix=dart-sass- $ _version / " ,
" --format=tar.gz " ,
_version
] ) ;
var digest = await sha256 . bind ( process . stdout ) . first ;
var stderr = await UTF8 . decodeStream ( process . stderr ) ;
if ( ( await process . exitCode ) ! = 0 ) {
fail ( ' git archive " $ _version " failed: \n $ stderr ' ) ;
}
if ( new Directory ( " build/homebrew-sass/.git " ) . existsSync ( ) ) {
await runAsync ( " git " ,
arguments: [ " fetch " , " origin " ] ,
workingDirectory: " build/homebrew-sass " ) ;
await runAsync ( " git " ,
arguments: [ " reset " , " --hard " , " origin/master " ] ,
workingDirectory: " build/homebrew-sass " ) ;
} else {
delete ( new Directory ( " build/homebrew-sass " ) ) ;
await runAsync ( " git " , arguments: [
" clone " ,
2018-02-04 02:02:27 +01:00
" https://github.com/sass/homebrew-sass.git " ,
2018-02-04 01:18:10 +01:00
" build/homebrew-sass "
] ) ;
}
var formula = new File ( " build/homebrew-sass/sass.rb " ) ;
log ( " updating ${ formula . path } " ) ;
formula . writeAsStringSync ( formula . readAsStringSync ( ) . replaceFirstMapped (
_homebrewRegExp ,
( match ) = >
' \n ${ match [ 1 ] } url "https://github.com/sass/dart-sass/archive/ $ _version .tar.gz" '
' \n ${ match [ 1 ] } sha256 " $ digest " ' ) ) ;
run ( " git " ,
arguments: [
" commit " ,
" --all " ,
" --message " ,
" Update Dart Sass to $ _version "
] ,
workingDirectory: " build/homebrew-sass " ) ;
await runAsync ( " git " ,
arguments: [ " push " ] , workingDirectory: " build/homebrew-sass " ) ;
}