<commit_before>// == Site Archive (*.star) file layout (subject to rapid change) ==
//
// /star.json
//
//  - format: "site-archive-pre1" for this version
//
//  - builtBy: human readable banner (eg, "Meteor 0.6.0")
//
//  - programs: array of programs in the star, each an object:
//    - name: short, unique name for program, for referring to it
//      programmatically
//    - arch: architecture that this program targets. Something like
//            "os", "os.linux.x86_64", or "browser.w3c".
//    - path: directory (relative to star.json) containing this program
//
//    XXX in the future this will also contain instructions for
//    mounting packages into the namespace of each program, and
//    possibly for mounting programs on top of each other (this would
//    be the principled mechanism by which a server program could read
//    a client program so it can server it)
//
//  - plugins: array of plugins in the star, each an object:
//    - name: short, unique name for plugin, for referring to it
//      programmatically
//    - arch: typically 'os' (for a portable plugin) or eg
//      'os.linux.x86_64' for one that include native node_modules
//    - path: path (relative to star.json) to the control file (eg,
//      program.json) for this plugin
//
// - meteorRelease: the value used in Meteor.release for programs inside the
//     star, or "none"
//
// /README: human readable instructions
//
// /main.js: script that can be run in node.js to start the site
//   running in standalone mode (after setting appropriate environment
//   variables as documented in README)
//
// /server/.bundle_version.txt: contains the dev_bundle version that
//   legacy (read: current) Galaxy version read in order to set
//   NODE_PATH to point to arch-specific builds of binary node modules
//   (primarily this is for node-fibers)
//
// XXX in the future one program (which must be a server-type
// architecture) will be designated as the 'init' program. The
// container will call it with arguments to signal app lifecycle
// events like 'start' and 'stop'.
//
//
// Conventionally programs will be located at /programs/<name>, but
// really the build tool can lay out the star however it wants.
//
//
// == Format of a program when arch is "browser.*" ==
//
// Standard:
//
// /program.json
//
//  - format: "browser-program-pre1" for this version
//
//  - page: path to the template for the HTML to serve when a browser
//    loads a page that is part of the application. In the file,
//    some strings of the format ##FOO## will be replaced with
//    appropriate values at runtime by the webapp package.
//
//  - manifest: array of resources to serve with HTTP, each an object:
//    - path: path of file relative to program.json
//    - where: "client"
//    - type: "js", "css", or "asset"
//    - cacheable: is it safe to ask the browser to cache this file (boolean)
//    - url: relative url to download the resource, includes cache busting
//        parameter when used
//    - size: size of file in bytes
//    - hash: sha1 hash of the file contents
//    - sourceMap: optional path to source map file (relative to program.json)
//    Additionally there will be an entry with where equal to
//    "internal", path equal to page (above), and hash equal to the
//    sha1 of page (before replacements.) Currently this is used to
//    trigger HTML5 appcache reloads at the right time (if the
//    'appcache' package is being used.)
//
// Convention:
//
// page is 'app.html'.
//
//
// == Format of a program when arch is "os.*" ==
//
// Standard:
//
// /server.js: script to run inside node.js to start the program
//
// XXX Subject to change! This will likely change to a shell script
// (allowing us to represent types of programs that don't use or
// depend on node) -- or in fact, rather than have anything fixed here
// at all, star.json just contains a path to a program to run, which
// can be anything than can be exec()'d.
//
// Convention:
//
// /program.json:
//
//  - load: array with each item describing a JS file to load at startup:
//    - path: path of file, relative to program.json
//    - node_modules: if Npm.require is called from this file, this is
//      the path (relative to program.json) of the directory that should
//      be search for npm modules
//    - assets: map from path (the argument to Assets.getText and
//      Assets.getBinary) to path on disk (relative to program.json)
//      of the asset
//    - sourceMap: if present, path of a file that contains a source
//      map for this file, relative to program.json
//
// /config.json:
//
//  - client: the client program that should be served up by HTTP,
//    expressed as a path (relative to program.json) to the *client's*
//    program.json.
//
//  - meteorRelease: the value to use for Meteor.release, if any
//
//
// /app/*: source code of the (server part of the) app
// /packages/foo.js: the (linked) source code for package foo
// /package-tests/foo.js: the (linked) source code for foo's tests
// /npm/foo/bar: node_modules for slice bar of package foo. may be
// symlinked if developing locally.
//
// /node_modules: node_modules needed for server.js. omitted if
// deploying (see .bundle_version.txt above), copied if bundling,
// symlinked if developing locally.
//
//
// == Format of a program that is to be used as a plugin ==
//
// /program.json:
//  - format: "javascript-image-pre1" for this version
//  - arch: the architecture that this build requires
//  - load: array with each item describing a JS file to load, in load order:
//    - path: path of file, relative to program.json
//    - node_modules: if Npm.require is called from this file, this is
//      the path (relative to program.json) of the directory that should
//      be search for npm modules
//
// It's a little odd that architecture is stored twice (in both the
// top-level star control file and in the plugin control file) but
// it's fine and it's probably actually cleaner, because it means that
// the plugin can be treated as a self-contained unit.
//
// Note that while the spec for "os.*" is going to change to
// represent an arbitrary POSIX (or Windows) process rather than
// assuming a nodejs host, these plugins will always refer to
// JavaScript code (that potentially might be a plugin to be loaded
// into an existing JS VM). But this seems to be a concern that is
// somewhat orthogonal to arch (these plugins can still use packages
// of arch "os.*".) There is probably a missing abstraction here
// somewhere (decoupling target type from architecture) but it can
// wait until later.

var path = require('path');
var files = require(path.join(__dirname, 'files.js'));
var packages = require(path.join(__dirname, 'packages.js'));
var linker = require(path.join(__dirname, 'linker.js'));
var Builder = require(path.join(__dirname, 'builder.js'));
var archinfo = require(path.join(__dirname, 'archinfo.js'));
var buildmessage = require('./buildmessage.js');
var fs = require('fs');
var _ = require('underscore');
var project = require(path.join(__dirname, 'project.js'));
var builder = require(path.join(__dirname, 'builder.js'));
var unipackage = require(path.join(__dirname, 'unipackage.js'));
var watch = require('./watch.js');
var Fiber = require('fibers');
var Future = require(path.join('fibers', 'future'));
var sourcemap = require('source-map');

// files to ignore when bundling. node has no globs, so use regexps
var ignoreFiles = [
    /~$/, /^\.#/, /^#.*#$/,
    /^\.DS_Store\/?$/, /^ehthumbs\.db$/, /^Icon.$/, /^Thumbs\.db$/,
    /^\.meteor\/$/, /* avoids scanning N^2 files when bundling all packages */
    /^\.git\/$/ /* often has too many files to watch */
];

// http://davidshariff.com/blog/javascript-inheritance-patterns/
var inherits = function (child, parent) {
  var tmp = function () {};
  tmp.prototype = parent.prototype;
  child.prototype = new tmp;
  child.prototype.constructor = child;
};

var rejectBadPath = function (p) {
  if (p.match(/\.\./))
    throw new Error("bad path: " + p);
};

var stripLeadingSlash = function (p) {
  if (p.charAt(0) !== '/')
    throw new Error("bad path: " + p);
  return p.slice(1);
};


///////////////////////////////////////////////////////////////////////////////
// NodeModulesDirectory
///////////////////////////////////////////////////////////////////////////////

// Represents a node_modules directory that we need to copy into the
// bundle or otherwise make available at runtime.
var NodeModulesDirectory = function (options) {
  var self = this;

  // The absolute path (on local disk) to a directory that contains
  // the built node_modules to use.
  self.sourcePath = options.sourcePath;

  // The path (relative to the bundle root) where we would preferably
  // like the node_modules to be output (essentially cosmetic.)
  self.preferredBundlePath = options.preferredBundlePath;
};

///////////////////////////////////////////////////////////////////////////////
// File
///////////////////////////////////////////////////////////////////////////////

// Allowed options:
// - sourcePath: path to file on disk that will provide our contents
// - data: contents of the file as a Buffer
// - sourceMap: if 'data' is given, can be given instead of sourcePath. a string
// - cacheable
var File = function (options) {
  var self = this;

  if (options.data && ! (options.data instanceof Buffer))
    throw new Error('File contents must be provided as a Buffer');
  if (! options.sourcePath && ! options.data)
    throw new Error("Must provide either sourcePath or data");

  // The absolute path in the filesystem from which we loaded (or will
  // load) this file (null if the file does not correspond to one on
  // disk.)
  self.sourcePath = options.sourcePath;

  // If this file was generated, a sourceMap (as a string) with debugging
  // information, as well as the "root" that paths in it should be resolved
  // against. Set with setSourceMap.
  self.sourceMap = null;
  self.sourceMapRoot = null;

  // Where this file is intended to reside within the target's
  // filesystem.
  self.targetPath = null;

  // The URL at which this file is intended to be served, relative to
  // the base URL at which the target is being served (ignored if this
  // file is not intended to be served over HTTP.)
  self.url = null;

  // Is this file guaranteed to never change, so that we can let it be
  // cached forever? Only makes sense of self.url is set.
  self.cacheable = options.cacheable || false;

  // The node_modules directory that Npm.require() should search when
  // called from inside this file, given as a NodeModulesDirectory, or
  // null if Npm.depend() is not in effect for this file. Only works
  // in the "server" architecture.
  self.nodeModulesDirectory = null;

  // For server JS only. Assets associated with this slice; map from the path
  // that is the argument to Assets.getBinary, to a Buffer that is its contents.
  self.assets = null;

  self._contents = options.data || null; // contents, if known, as a Buffer
  self._hash = null; // hash, if known, as a hex string
};

_.extend(File.prototype, {
  hash: function () {
    var self = this;
    if (! self._hash)
      self._hash = Builder.sha1(self.contents());
    return self._hash;
  },

  // Omit encoding to get a buffer, or provide something like 'utf8'
  // to get a string
  contents: function (encoding) {
    var self = this;
    if (! self._contents) {
      if (! self.sourcePath) {
        throw new Error("Have neither contents nor sourcePath for file");
      }
      else
        self._contents = fs.readFileSync(self.sourcePath);
    }

    return encoding ? self._contents.toString(encoding) : self._contents;
  },

  setContents: function (b) {
    var self = this;
    if (!(b instanceof Buffer))
      throw new Error("Must set contents to a Buffer");
    self._contents = b;
    // Un-cache hash.
    self._hash = null;
  },

  size: function () {
    var self = this;
    return self.contents().length;
  },

  // Set the URL of this file to "/<hash><suffix>". suffix will
  // typically be used to pick a reasonable extension. Also set
  // cacheable to true, since the file's name is now derived from its
  // contents.
  setUrlToHash: function (suffix) {
    var self = this;
    self.url = "/" + self.hash() + suffix;
    self.cacheable = true;
    self.targetPath = self.hash() + suffix;
  },

  // Append "?<hash>" to the URL and mark the file as cacheable.
  addCacheBuster: function () {
    var self = this;
    if (! self.url)
      throw new Error("File must have a URL");
    if (self.cacheable)
      return; // eg, already got setUrlToHash
    if (/\?/.test(self.url))
      throw new Error("URL already has a query string");
    self.url += "?" + self.hash();
    self.cacheable = true;
  },

  // Given a relative path like 'a/b/c' (where '/' is this system's
  // path component separator), produce a URL that always starts with
  // a forward slash and that uses a literal forward slash as the
  // component separator.
  setUrlFromRelPath: function (relPath) {
    var self = this;
    var url = relPath.split(path.sep).join('/');

    if (url.charAt(0) !== '/')
      url = '/' + url;

    self.url = url;
  },

  setTargetPathFromRelPath: function (relPath) {
    var self = this;
    // XXX hack
    if (relPath.match(/^packages\//) || relPath.match(/^assets\//))
      self.targetPath = relPath;
    else
      self.targetPath = path.join('app', relPath);
  },

  // Set a source map for this File. sourceMap is given as a string.
  setSourceMap: function (sourceMap, root) {
    var self = this;

    if (typeof sourceMap !== "string")
      throw new Error("sourceMap must be given as a string");
    self.sourceMap = sourceMap;
    self.sourceMapRoot = root;
  },

  // note: this assets object may be shared among multiple files!
  setAssets: function (assets) {
    var self = this;
    if (!_.isEmpty(assets))
      self.assets = assets;
  }
});

///////////////////////////////////////////////////////////////////////////////
// Target
///////////////////////////////////////////////////////////////////////////////

// options:
// - library: package library to use for resolving package dependenices
// - arch: the architecture to build
//
// see subclasses for additional options
var Target = function (options) {
  var self = this;

  // Package library to use for resolving package dependenices.
  self.library = options.library;

  // Something like "browser.w3c" or "os" or "os.osx.x86_64"
  self.arch = options.arch;

  // All of the Slices that are to go into this target, in the order
  // that they are to be loaded.
  self.slices = [];

  // JavaScript files. List of File. They will be loaded at startup in
  // the order given.
  self.js = [];

  // On-disk dependencies of this target.
  self.watchSet = new watch.WatchSet();

  // Map from package name to package directory of all packages used.
  self.pluginProviderPackageDirs = {};

  // node_modules directories that we need to copy into the target (or
  // otherwise make available at runtime.) A map from an absolute path
  // on disk (NodeModulesDirectory.sourcePath) to a
  // NodeModulesDirectory object that we have created to represent it.
  self.nodeModulesDirectories = {};

  // Static assets to include in the bundle. List of File.
  // For browser targets, these are served over HTTP.
  self.asset = [];
};

_.extend(Target.prototype, {
  // Top-level entry point for building a target. Generally to build a
  // target, you create with 'new', call make() to specify its sources
  // and build options and actually do the work of buliding the
  // target, and finally you retrieve the build product with a
  // target-type-dependent function such as write() or toJsImage().
  //
  // options
  // - packages: packages to include (Package or 'foo' or 'foo.slice'),
  //   per _determineLoadOrder
  // - test: packages to test (Package or 'foo'), per _determineLoadOrder
  // - minify: true to minify
  // - addCacheBusters: if true, make all files cacheable by adding
  //   unique query strings to their URLs. unlikely to be of much use
  //   on server targets.
  make: function (options) {
    var self = this;

    // Populate the list of slices to load
    self._determineLoadOrder({
      packages: options.packages || [],
      test: options.test || []
    });

    // Link JavaScript and set up self.js, etc.
    self._emitResources();

    // Minify, if requested
    if (options.minify) {
      var minifiers = unipackage.load({
        library: self.library,
        packages: ['minifiers']
      }).minifiers;
      self.minifyJs(minifiers);
      if (self.minifyCss) // XXX a bit of a hack
        self.minifyCss(minifiers);
    }

    if (options.addCacheBusters) {
      // Make client-side CSS and JS assets cacheable forever, by
      // adding a query string with a cache-busting hash.
      self._addCacheBusters("js");
      self._addCacheBusters("css");
    }
  },

  // Determine the packages to load, create Slices for
  // them, put them in load order, save in slices.
  //
  // options include:
  // - packages: an array of packages (or, properly speaking, slices)
  //   to include. Each element should either be a Package object or a
  //   package name as a string (to include that package's default
  //   slices for this arch, or a string of the form 'package.slice'
  //   to include a particular named slice from a particular package.
  // - test: an array of packages (as Package objects or as name
  //   strings) whose test slices should be included
  _determineLoadOrder: function (options) {
    var self = this;
    var library = self.library;

    // Find the roots
    var rootSlices =
      _.flatten([
        _.map(options.packages || [], function (p) {
          if (typeof p === "string")
            return library.getSlices(p, self.arch);
          else
            return p.getDefaultSlices(self.arch);
        }),
        _.map(options.test || [], function (p) {
          var pkg = (typeof p === "string" ? library.get(p) : p);
          return pkg.getTestSlices(self.arch);
        })
      ]);

    // PHASE 1: Which slices will be used?
    //
    // Figure out which slices are going to be used in the target, regardless of
    // order. We ignore weak dependencies here, because they don't actually
    // create a "must-use" constraint, just an ordering constraint.

    // What slices will be used in the target? Built in Phase 1, read in
    // Phase 2.
    var getsUsed = {};  // Map from slice.id to Slice.
    var addToGetsUsed = function (slice) {
      if (_.has(getsUsed, slice.id))
        return;
      getsUsed[slice.id] = slice;
      slice.eachUsedSlice(self.arch, {skipWeak: true}, addToGetsUsed);
    };
    _.each(rootSlices, addToGetsUsed);

    // PHASE 2: In what order should we load the slices?
    //
    // Set self.slices to be all of the roots, plus all of their non-weak
    // dependencies, in the correct load order. "Load order" means that if X
    // depends on (uses) Y, and that relationship is not marked as unordered, Y
    // appears before X in the ordering. Raises an exception iff there is no
    // such ordering (due to circular dependency.)

    // What slices have not yet been added to self.slices?
    var needed = _.clone(getsUsed);  // Map from slice.id to Slice.
    // Slices that we are in the process of adding; used to detect circular
    // ordered dependencies.
    var onStack = {};  // Map from slice.id to true.

    // This helper recursively adds slice's ordered dependencies to self.slices,
    // then adds slice itself.
    var add = function (slice) {
      // If this has already been added, there's nothing to do.
      if (!_.has(needed, slice.id))
        return;

      // Process each ordered dependency. (If we have an unordered dependency
      // `u`, then there's no reason to add it *now*, and for all we know, `u`
      // will depend on `slice` and need to be added after it. So we ignore
      // those edge. Because we did follow those edges in Phase 1, any unordered
      // slices were at some point in `needed` and will not be left out.)
      slice.eachUsedSlice(
        self.arch, {skipUnordered: true}, function (usedSlice, useOptions) {
          // If this is a weak dependency, and nothing else in the target had a
          // strong dependency on it, then ignore this edge.
          if (useOptions.weak && ! _.has(getsUsed, usedSlice.id))
            return;
          if (onStack[usedSlice.id]) {
            buildmessage.error("circular dependency between packages " +
                               slice.pkg.name + " and " + usedSlice.pkg.name);
            // recover by not enforcing one of the depedencies
            return;
          }
          onStack[usedSlice.id] = true;
          add(usedSlice);
          delete onStack[usedSlice.id];
        });
      self.slices.push(slice);
      delete needed[slice.id];
    };

    while (true) {
      // Get an arbitrary slice from those that remain, or break if none remain.
      var first = null;
      for (first in needed) break;
      if (! first)
        break;
      // Now add it, after its ordered dependencies.
      add(needed[first]);
    }
  },

  // Process all of the sorted slices (which includes running the JavaScript
  // linker).
  _emitResources: function () {
    var self = this;

    var isBrowser = archinfo.matches(self.arch, "browser");
    var isOs = archinfo.matches(self.arch, "os");

    // Copy their resources into the bundle in order
    _.each(self.slices, function (slice) {
      var isApp = ! slice.pkg.name;

      // Emit the resources
      var resources = slice.getResources(self.arch);

      // First, find all the assets, so that we can associate them with each js
      // resource (for os slices).
      var sliceAssets = {};
      _.each(resources, function (resource) {
        if (resource.type !== "asset")
          return;

        var f = new File({data: resource.data, cacheable: false});

        var relPath = isOs
              ? path.join("assets", resource.servePath)
              : stripLeadingSlash(resource.servePath);
        f.setTargetPathFromRelPath(relPath);

        if (isBrowser)
          f.setUrlFromRelPath(resource.servePath);
        else {
          sliceAssets[resource.path] = resource.data;
        }

        self.asset.push(f);
      });

      // Now look for the other kinds of resources.
      _.each(resources, function (resource) {
        if (resource.type === "asset")
          return;  // already handled

        if (_.contains(["js", "css"], resource.type)) {
          if (resource.type === "css" && ! isBrowser)
            // XXX might be nice to throw an error here, but then we'd
            // have to make it so that packages.js ignores css files
            // that appear in the server directories in an app tree

            // XXX XXX can't we easily do that in the css handler in
            // meteor.js?
            return;

          var f = new File({data: resource.data, cacheable: false});

          var relPath = stripLeadingSlash(resource.servePath);
          f.setTargetPathFromRelPath(relPath);

          if (isBrowser) {
            f.setUrlFromRelPath(resource.servePath);
          }

          if (resource.type === "js" && isOs) {
            // Hack, but otherwise we'll end up putting app assets on this file.
            if (resource.servePath !== "/packages/global-imports.js")
              f.setAssets(sliceAssets);

            if (! isApp && slice.nodeModulesPath) {
              var nmd = self.nodeModulesDirectories[slice.nodeModulesPath];
              if (! nmd) {
                nmd = new NodeModulesDirectory({
                  sourcePath: slice.nodeModulesPath,
                  // It's important that this path end with
                  // node_modules. Otherwise, if two modules in this package
                  // depend on each other, they won't be able to find each
                  // other!
                  preferredBundlePath: path.join(
                    'npm', slice.pkg.name, slice.sliceName, 'node_modules')
                });
                self.nodeModulesDirectories[slice.nodeModulesPath] = nmd;
              }
              f.nodeModulesDirectory = nmd;
            }
          }

          if (resource.type === "js" && resource.sourceMap) {
            f.setSourceMap(resource.sourceMap, path.dirname(relPath));
          }

          self[resource.type].push(f);
          return;
        }

        if (_.contains(["head", "body"], resource.type)) {
          if (! isBrowser)
            throw new Error("HTML segments can only go to the browser");
          self[resource.type].push(resource.data);
          return;
        }

        throw new Error("Unknown type " + resource.type);
      });

      // Depend on the source files that produced these resources.
      self.watchSet.merge(slice.watchSet);
      // Remember the library resolution of all packages used in these
      // resources.
      // XXX assumes that this merges cleanly
      _.extend(self.pluginProviderPackageDirs,
               slice.pkg.pluginProviderPackageDirs)
    });
  },

  // Minify the JS in this target
  minifyJs: function (minifiers) {
    var self = this;

    var allJs = _.map(self.js, function (file) {
      return file.contents('utf8');
    }).join('\n;\n');

    allJs = minifiers.UglifyJSMinify(allJs, {
      fromString: true,
      compress: {drop_debugger: false}
    }).code;

    self.js = [new File({ data: new Buffer(allJs, 'utf8') })];
    self.js[0].setUrlToHash(".js");
  },

  // For each resource of the given type, make it cacheable by adding
  // a query string to the URL based on its hash.
  _addCacheBusters: function (type) {
    var self = this;
    _.each(self[type], function (file) {
      file.addCacheBuster();
    });
  },

  // Return the WatchSet for this target's dependency info.
  getWatchSet: function () {
    var self = this;
    return self.watchSet;
  },

  getPluginProviderPackageDirs: function () {
    var self = this;
    return self.pluginProviderPackageDirs;
  },

  // Return the most inclusive architecture with which this target is
  // compatible. For example, if we set out to build a
  // 'os.linux.x86_64' version of this target (by passing that as
  // the 'arch' argument to the constructor), but ended up not
  // including anything that was specific to Linux, the return value
  // would be 'os'.
  mostCompatibleArch: function () {
    var self = this;
    return archinfo.leastSpecificDescription(_.pluck(self.slices, 'arch'));
  }
});


//////////////////// ClientTarget ////////////////////

var ClientTarget = function (options) {
  var self = this;
  Target.apply(this, arguments);

  // CSS files. List of File. They will be loaded at page load in the
  // order given.
  self.css = [];

  // List of segments of additional HTML for <head>/<body>.
  self.head = [];
  self.body = [];

  if (! archinfo.matches(self.arch, "browser"))
    throw new Error("ClientTarget targeting something that isn't a browser?");
};

inherits(ClientTarget, Target);

_.extend(ClientTarget.prototype, {
  // Minify the CSS in this target
  minifyCss: function (minifiers) {
    var self = this;

    // after concatenation some @import's might be left in the middle of CSS
    // file but they required to be in the beginning.
    var importsAst = {
      type: 'stylesheet',
      stylesheet: { rules: [] }
    };

    var cssAsts = _.map(self.css, function (file) {
      var ast = minifiers.CssParse(file.contents('utf8'));
      function isImportRule (node) {<commit_msg>Error reporting for CSS parsing<commit_after>// == Site Archive (*.star) file layout (subject to rapid change) ==
//
// /star.json
//
//  - format: "site-archive-pre1" for this version
//
//  - builtBy: human readable banner (eg, "Meteor 0.6.0")
//
//  - programs: array of programs in the star, each an object:
//    - name: short, unique name for program, for referring to it
//      programmatically
//    - arch: architecture that this program targets. Something like
//            "os", "os.linux.x86_64", or "browser.w3c".
//    - path: directory (relative to star.json) containing this program
//
//    XXX in the future this will also contain instructions for
//    mounting packages into the namespace of each program, and
//    possibly for mounting programs on top of each other (this would
//    be the principled mechanism by which a server program could read
//    a client program so it can server it)
//
//  - plugins: array of plugins in the star, each an object:
//    - name: short, unique name for plugin, for referring to it
//      programmatically
//    - arch: typically 'os' (for a portable plugin) or eg
//      'os.linux.x86_64' for one that include native node_modules
//    - path: path (relative to star.json) to the control file (eg,
//      program.json) for this plugin
//
// - meteorRelease: the value used in Meteor.release for programs inside the
//     star, or "none"
//
// /README: human readable instructions
//
// /main.js: script that can be run in node.js to start the site
//   running in standalone mode (after setting appropriate environment
//   variables as documented in README)
//
// /server/.bundle_version.txt: contains the dev_bundle version that
//   legacy (read: current) Galaxy version read in order to set
//   NODE_PATH to point to arch-specific builds of binary node modules
//   (primarily this is for node-fibers)
//
// XXX in the future one program (which must be a server-type
// architecture) will be designated as the 'init' program. The
// container will call it with arguments to signal app lifecycle
// events like 'start' and 'stop'.
//
//
// Conventionally programs will be located at /programs/<name>, but
// really the build tool can lay out the star however it wants.
//
//
// == Format of a program when arch is "browser.*" ==
//
// Standard:
//
// /program.json
//
//  - format: "browser-program-pre1" for this version
//
//  - page: path to the template for the HTML to serve when a browser
//    loads a page that is part of the application. In the file,
//    some strings of the format ##FOO## will be replaced with
//    appropriate values at runtime by the webapp package.
//
//  - manifest: array of resources to serve with HTTP, each an object:
//    - path: path of file relative to program.json
//    - where: "client"
//    - type: "js", "css", or "asset"
//    - cacheable: is it safe to ask the browser to cache this file (boolean)
//    - url: relative url to download the resource, includes cache busting
//        parameter when used
//    - size: size of file in bytes
//    - hash: sha1 hash of the file contents
//    - sourceMap: optional path to source map file (relative to program.json)
//    Additionally there will be an entry with where equal to
//    "internal", path equal to page (above), and hash equal to the
//    sha1 of page (before replacements.) Currently this is used to
//    trigger HTML5 appcache reloads at the right time (if the
//    'appcache' package is being used.)
//
// Convention:
//
// page is 'app.html'.
//
//
// == Format of a program when arch is "os.*" ==
//
// Standard:
//
// /server.js: script to run inside node.js to start the program
//
// XXX Subject to change! This will likely change to a shell script
// (allowing us to represent types of programs that don't use or
// depend on node) -- or in fact, rather than have anything fixed here
// at all, star.json just contains a path to a program to run, which
// can be anything than can be exec()'d.
//
// Convention:
//
// /program.json:
//
//  - load: array with each item describing a JS file to load at startup:
//    - path: path of file, relative to program.json
//    - node_modules: if Npm.require is called from this file, this is
//      the path (relative to program.json) of the directory that should
//      be search for npm modules
//    - assets: map from path (the argument to Assets.getText and
//      Assets.getBinary) to path on disk (relative to program.json)
//      of the asset
//    - sourceMap: if present, path of a file that contains a source
//      map for this file, relative to program.json
//
// /config.json:
//
//  - client: the client program that should be served up by HTTP,
//    expressed as a path (relative to program.json) to the *client's*
//    program.json.
//
//  - meteorRelease: the value to use for Meteor.release, if any
//
//
// /app/*: source code of the (server part of the) app
// /packages/foo.js: the (linked) source code for package foo
// /package-tests/foo.js: the (linked) source code for foo's tests
// /npm/foo/bar: node_modules for slice bar of package foo. may be
// symlinked if developing locally.
//
// /node_modules: node_modules needed for server.js. omitted if
// deploying (see .bundle_version.txt above), copied if bundling,
// symlinked if developing locally.
//
//
// == Format of a program that is to be used as a plugin ==
//
// /program.json:
//  - format: "javascript-image-pre1" for this version
//  - arch: the architecture that this build requires
//  - load: array with each item describing a JS file to load, in load order:
//    - path: path of file, relative to program.json
//    - node_modules: if Npm.require is called from this file, this is
//      the path (relative to program.json) of the directory that should
//      be search for npm modules
//
// It's a little odd that architecture is stored twice (in both the
// top-level star control file and in the plugin control file) but
// it's fine and it's probably actually cleaner, because it means that
// the plugin can be treated as a self-contained unit.
//
// Note that while the spec for "os.*" is going to change to
// represent an arbitrary POSIX (or Windows) process rather than
// assuming a nodejs host, these plugins will always refer to
// JavaScript code (that potentially might be a plugin to be loaded
// into an existing JS VM). But this seems to be a concern that is
// somewhat orthogonal to arch (these plugins can still use packages
// of arch "os.*".) There is probably a missing abstraction here
// somewhere (decoupling target type from architecture) but it can
// wait until later.

var path = require('path');
var util = require('util');
var files = require(path.join(__dirname, 'files.js'));
var packages = require(path.join(__dirname, 'packages.js'));
var linker = require(path.join(__dirname, 'linker.js'));
var Builder = require(path.join(__dirname, 'builder.js'));
var archinfo = require(path.join(__dirname, 'archinfo.js'));
var buildmessage = require('./buildmessage.js');
var fs = require('fs');
var _ = require('underscore');
var project = require(path.join(__dirname, 'project.js'));
var builder = require(path.join(__dirname, 'builder.js'));
var unipackage = require(path.join(__dirname, 'unipackage.js'));
var watch = require('./watch.js');
var Fiber = require('fibers');
var Future = require(path.join('fibers', 'future'));
var sourcemap = require('source-map');

// files to ignore when bundling. node has no globs, so use regexps
var ignoreFiles = [
    /~$/, /^\.#/, /^#.*#$/,
    /^\.DS_Store\/?$/, /^ehthumbs\.db$/, /^Icon.$/, /^Thumbs\.db$/,
    /^\.meteor\/$/, /* avoids scanning N^2 files when bundling all packages */
    /^\.git\/$/ /* often has too many files to watch */
];

// http://davidshariff.com/blog/javascript-inheritance-patterns/
var inherits = function (child, parent) {
  var tmp = function () {};
  tmp.prototype = parent.prototype;
  child.prototype = new tmp;
  child.prototype.constructor = child;
};

var rejectBadPath = function (p) {
  if (p.match(/\.\./))
    throw new Error("bad path: " + p);
};

var stripLeadingSlash = function (p) {
  if (p.charAt(0) !== '/')
    throw new Error("bad path: " + p);
  return p.slice(1);
};


///////////////////////////////////////////////////////////////////////////////
// NodeModulesDirectory
///////////////////////////////////////////////////////////////////////////////

// Represents a node_modules directory that we need to copy into the
// bundle or otherwise make available at runtime.
var NodeModulesDirectory = function (options) {
  var self = this;

  // The absolute path (on local disk) to a directory that contains
  // the built node_modules to use.
  self.sourcePath = options.sourcePath;

  // The path (relative to the bundle root) where we would preferably
  // like the node_modules to be output (essentially cosmetic.)
  self.preferredBundlePath = options.preferredBundlePath;
};

///////////////////////////////////////////////////////////////////////////////
// File
///////////////////////////////////////////////////////////////////////////////

// Allowed options:
// - sourcePath: path to file on disk that will provide our contents
// - data: contents of the file as a Buffer
// - sourceMap: if 'data' is given, can be given instead of sourcePath. a string
// - cacheable
var File = function (options) {
  var self = this;

  if (options.data && ! (options.data instanceof Buffer))
    throw new Error('File contents must be provided as a Buffer');
  if (! options.sourcePath && ! options.data)
    throw new Error("Must provide either sourcePath or data");

  // The absolute path in the filesystem from which we loaded (or will
  // load) this file (null if the file does not correspond to one on
  // disk.)
  self.sourcePath = options.sourcePath;

  // If this file was generated, a sourceMap (as a string) with debugging
  // information, as well as the "root" that paths in it should be resolved
  // against. Set with setSourceMap.
  self.sourceMap = null;
  self.sourceMapRoot = null;

  // Where this file is intended to reside within the target's
  // filesystem.
  self.targetPath = null;

  // The URL at which this file is intended to be served, relative to
  // the base URL at which the target is being served (ignored if this
  // file is not intended to be served over HTTP.)
  self.url = null;

  // Is this file guaranteed to never change, so that we can let it be
  // cached forever? Only makes sense of self.url is set.
  self.cacheable = options.cacheable || false;

  // The node_modules directory that Npm.require() should search when
  // called from inside this file, given as a NodeModulesDirectory, or
  // null if Npm.depend() is not in effect for this file. Only works
  // in the "server" architecture.
  self.nodeModulesDirectory = null;

  // For server JS only. Assets associated with this slice; map from the path
  // that is the argument to Assets.getBinary, to a Buffer that is its contents.
  self.assets = null;

  self._contents = options.data || null; // contents, if known, as a Buffer
  self._hash = null; // hash, if known, as a hex string
};

_.extend(File.prototype, {
  hash: function () {
    var self = this;
    if (! self._hash)
      self._hash = Builder.sha1(self.contents());
    return self._hash;
  },

  // Omit encoding to get a buffer, or provide something like 'utf8'
  // to get a string
  contents: function (encoding) {
    var self = this;
    if (! self._contents) {
      if (! self.sourcePath) {
        throw new Error("Have neither contents nor sourcePath for file");
      }
      else
        self._contents = fs.readFileSync(self.sourcePath);
    }

    return encoding ? self._contents.toString(encoding) : self._contents;
  },

  setContents: function (b) {
    var self = this;
    if (!(b instanceof Buffer))
      throw new Error("Must set contents to a Buffer");
    self._contents = b;
    // Un-cache hash.
    self._hash = null;
  },

  size: function () {
    var self = this;
    return self.contents().length;
  },

  // Set the URL of this file to "/<hash><suffix>". suffix will
  // typically be used to pick a reasonable extension. Also set
  // cacheable to true, since the file's name is now derived from its
  // contents.
  setUrlToHash: function (suffix) {
    var self = this;
    self.url = "/" + self.hash() + suffix;
    self.cacheable = true;
    self.targetPath = self.hash() + suffix;
  },

  // Append "?<hash>" to the URL and mark the file as cacheable.
  addCacheBuster: function () {
    var self = this;
    if (! self.url)
      throw new Error("File must have a URL");
    if (self.cacheable)
      return; // eg, already got setUrlToHash
    if (/\?/.test(self.url))
      throw new Error("URL already has a query string");
    self.url += "?" + self.hash();
    self.cacheable = true;
  },

  // Given a relative path like 'a/b/c' (where '/' is this system's
  // path component separator), produce a URL that always starts with
  // a forward slash and that uses a literal forward slash as the
  // component separator.
  setUrlFromRelPath: function (relPath) {
    var self = this;
    var url = relPath.split(path.sep).join('/');

    if (url.charAt(0) !== '/')
      url = '/' + url;

    self.url = url;
  },

  setTargetPathFromRelPath: function (relPath) {
    var self = this;
    // XXX hack
    if (relPath.match(/^packages\//) || relPath.match(/^assets\//))
      self.targetPath = relPath;
    else
      self.targetPath = path.join('app', relPath);
  },

  // Set a source map for this File. sourceMap is given as a string.
  setSourceMap: function (sourceMap, root) {
    var self = this;

    if (typeof sourceMap !== "string")
      throw new Error("sourceMap must be given as a string");
    self.sourceMap = sourceMap;
    self.sourceMapRoot = root;
  },

  // note: this assets object may be shared among multiple files!
  setAssets: function (assets) {
    var self = this;
    if (!_.isEmpty(assets))
      self.assets = assets;
  }
});

///////////////////////////////////////////////////////////////////////////////
// Target
///////////////////////////////////////////////////////////////////////////////

// options:
// - library: package library to use for resolving package dependenices
// - arch: the architecture to build
//
// see subclasses for additional options
var Target = function (options) {
  var self = this;

  // Package library to use for resolving package dependenices.
  self.library = options.library;

  // Something like "browser.w3c" or "os" or "os.osx.x86_64"
  self.arch = options.arch;

  // All of the Slices that are to go into this target, in the order
  // that they are to be loaded.
  self.slices = [];

  // JavaScript files. List of File. They will be loaded at startup in
  // the order given.
  self.js = [];

  // On-disk dependencies of this target.
  self.watchSet = new watch.WatchSet();

  // Map from package name to package directory of all packages used.
  self.pluginProviderPackageDirs = {};

  // node_modules directories that we need to copy into the target (or
  // otherwise make available at runtime.) A map from an absolute path
  // on disk (NodeModulesDirectory.sourcePath) to a
  // NodeModulesDirectory object that we have created to represent it.
  self.nodeModulesDirectories = {};

  // Static assets to include in the bundle. List of File.
  // For browser targets, these are served over HTTP.
  self.asset = [];
};

_.extend(Target.prototype, {
  // Top-level entry point for building a target. Generally to build a
  // target, you create with 'new', call make() to specify its sources
  // and build options and actually do the work of buliding the
  // target, and finally you retrieve the build product with a
  // target-type-dependent function such as write() or toJsImage().
  //
  // options
  // - packages: packages to include (Package or 'foo' or 'foo.slice'),
  //   per _determineLoadOrder
  // - test: packages to test (Package or 'foo'), per _determineLoadOrder
  // - minify: true to minify
  // - addCacheBusters: if true, make all files cacheable by adding
  //   unique query strings to their URLs. unlikely to be of much use
  //   on server targets.
  make: function (options) {
    var self = this;

    // Populate the list of slices to load
    self._determineLoadOrder({
      packages: options.packages || [],
      test: options.test || []
    });

    // Link JavaScript and set up self.js, etc.
    self._emitResources();

    // Minify, if requested
    if (options.minify) {
      var minifiers = unipackage.load({
        library: self.library,
        packages: ['minifiers']
      }).minifiers;
      self.minifyJs(minifiers);
      if (self.minifyCss) // XXX a bit of a hack
        self.minifyCss(minifiers);
    }

    if (options.addCacheBusters) {
      // Make client-side CSS and JS assets cacheable forever, by
      // adding a query string with a cache-busting hash.
      self._addCacheBusters("js");
      self._addCacheBusters("css");
    }
  },

  // Determine the packages to load, create Slices for
  // them, put them in load order, save in slices.
  //
  // options include:
  // - packages: an array of packages (or, properly speaking, slices)
  //   to include. Each element should either be a Package object or a
  //   package name as a string (to include that package's default
  //   slices for this arch, or a string of the form 'package.slice'
  //   to include a particular named slice from a particular package.
  // - test: an array of packages (as Package objects or as name
  //   strings) whose test slices should be included
  _determineLoadOrder: function (options) {
    var self = this;
    var library = self.library;

    // Find the roots
    var rootSlices =
      _.flatten([
        _.map(options.packages || [], function (p) {
          if (typeof p === "string")
            return library.getSlices(p, self.arch);
          else
            return p.getDefaultSlices(self.arch);
        }),
        _.map(options.test || [], function (p) {
          var pkg = (typeof p === "string" ? library.get(p) : p);
          return pkg.getTestSlices(self.arch);
        })
      ]);

    // PHASE 1: Which slices will be used?
    //
    // Figure out which slices are going to be used in the target, regardless of
    // order. We ignore weak dependencies here, because they don't actually
    // create a "must-use" constraint, just an ordering constraint.

    // What slices will be used in the target? Built in Phase 1, read in
    // Phase 2.
    var getsUsed = {};  // Map from slice.id to Slice.
    var addToGetsUsed = function (slice) {
      if (_.has(getsUsed, slice.id))
        return;
      getsUsed[slice.id] = slice;
      slice.eachUsedSlice(self.arch, {skipWeak: true}, addToGetsUsed);
    };
    _.each(rootSlices, addToGetsUsed);

    // PHASE 2: In what order should we load the slices?
    //
    // Set self.slices to be all of the roots, plus all of their non-weak
    // dependencies, in the correct load order. "Load order" means that if X
    // depends on (uses) Y, and that relationship is not marked as unordered, Y
    // appears before X in the ordering. Raises an exception iff there is no
    // such ordering (due to circular dependency.)

    // What slices have not yet been added to self.slices?
    var needed = _.clone(getsUsed);  // Map from slice.id to Slice.
    // Slices that we are in the process of adding; used to detect circular
    // ordered dependencies.
    var onStack = {};  // Map from slice.id to true.

    // This helper recursively adds slice's ordered dependencies to self.slices,
    // then adds slice itself.
    var add = function (slice) {
      // If this has already been added, there's nothing to do.
      if (!_.has(needed, slice.id))
        return;

      // Process each ordered dependency. (If we have an unordered dependency
      // `u`, then there's no reason to add it *now*, and for all we know, `u`
      // will depend on `slice` and need to be added after it. So we ignore
      // those edge. Because we did follow those edges in Phase 1, any unordered
      // slices were at some point in `needed` and will not be left out.)
      slice.eachUsedSlice(
        self.arch, {skipUnordered: true}, function (usedSlice, useOptions) {
          // If this is a weak dependency, and nothing else in the target had a
          // strong dependency on it, then ignore this edge.
          if (useOptions.weak && ! _.has(getsUsed, usedSlice.id))
            return;
          if (onStack[usedSlice.id]) {
            buildmessage.error("circular dependency between packages " +
                               slice.pkg.name + " and " + usedSlice.pkg.name);
            // recover by not enforcing one of the depedencies
            return;
          }
          onStack[usedSlice.id] = true;
          add(usedSlice);
          delete onStack[usedSlice.id];
        });
      self.slices.push(slice);
      delete needed[slice.id];
    };

    while (true) {
      // Get an arbitrary slice from those that remain, or break if none remain.
      var first = null;
      for (first in needed) break;
      if (! first)
        break;
      // Now add it, after its ordered dependencies.
      add(needed[first]);
    }
  },

  // Process all of the sorted slices (which includes running the JavaScript
  // linker).
  _emitResources: function () {
    var self = this;

    var isBrowser = archinfo.matches(self.arch, "browser");
    var isOs = archinfo.matches(self.arch, "os");

    // Copy their resources into the bundle in order
    _.each(self.slices, function (slice) {
      var isApp = ! slice.pkg.name;

      // Emit the resources
      var resources = slice.getResources(self.arch);

      // First, find all the assets, so that we can associate them with each js
      // resource (for os slices).
      var sliceAssets = {};
      _.each(resources, function (resource) {
        if (resource.type !== "asset")
          return;

        var f = new File({data: resource.data, cacheable: false});

        var relPath = isOs
              ? path.join("assets", resource.servePath)
              : stripLeadingSlash(resource.servePath);
        f.setTargetPathFromRelPath(relPath);

        if (isBrowser)
          f.setUrlFromRelPath(resource.servePath);
        else {
          sliceAssets[resource.path] = resource.data;
        }

        self.asset.push(f);
      });

      // Now look for the other kinds of resources.
      _.each(resources, function (resource) {
        if (resource.type === "asset")
          return;  // already handled

        if (_.contains(["js", "css"], resource.type)) {
          if (resource.type === "css" && ! isBrowser)
            // XXX might be nice to throw an error here, but then we'd
            // have to make it so that packages.js ignores css files
            // that appear in the server directories in an app tree

            // XXX XXX can't we easily do that in the css handler in
            // meteor.js?
            return;

          var f = new File({data: resource.data, cacheable: false});

          var relPath = stripLeadingSlash(resource.servePath);
          f.setTargetPathFromRelPath(relPath);

          if (isBrowser) {
            f.setUrlFromRelPath(resource.servePath);
          }

          if (resource.type === "js" && isOs) {
            // Hack, but otherwise we'll end up putting app assets on this file.
            if (resource.servePath !== "/packages/global-imports.js")
              f.setAssets(sliceAssets);

            if (! isApp && slice.nodeModulesPath) {
              var nmd = self.nodeModulesDirectories[slice.nodeModulesPath];
              if (! nmd) {
                nmd = new NodeModulesDirectory({
                  sourcePath: slice.nodeModulesPath,
                  // It's important that this path end with
                  // node_modules. Otherwise, if two modules in this package
                  // depend on each other, they won't be able to find each
                  // other!
                  preferredBundlePath: path.join(
                    'npm', slice.pkg.name, slice.sliceName, 'node_modules')
                });
                self.nodeModulesDirectories[slice.nodeModulesPath] = nmd;
              }
              f.nodeModulesDirectory = nmd;
            }
          }

          if (resource.type === "js" && resource.sourceMap) {
            f.setSourceMap(resource.sourceMap, path.dirname(relPath));
          }

          self[resource.type].push(f);
          return;
        }

        if (_.contains(["head", "body"], resource.type)) {
          if (! isBrowser)
            throw new Error("HTML segments can only go to the browser");
          self[resource.type].push(resource.data);
          return;
        }

        throw new Error("Unknown type " + resource.type);
      });

      // Depend on the source files that produced these resources.
      self.watchSet.merge(slice.watchSet);
      // Remember the library resolution of all packages used in these
      // resources.
      // XXX assumes that this merges cleanly
      _.extend(self.pluginProviderPackageDirs,
               slice.pkg.pluginProviderPackageDirs)
    });
  },

  // Minify the JS in this target
  minifyJs: function (minifiers) {
    var self = this;

    var allJs = _.map(self.js, function (file) {
      return file.contents('utf8');
    }).join('\n;\n');

    allJs = minifiers.UglifyJSMinify(allJs, {
      fromString: true,
      compress: {drop_debugger: false}
    }).code;

    self.js = [new File({ data: new Buffer(allJs, 'utf8') })];
    self.js[0].setUrlToHash(".js");
  },

  // For each resource of the given type, make it cacheable by adding
  // a query string to the URL based on its hash.
  _addCacheBusters: function (type) {
    var self = this;
    _.each(self[type], function (file) {
      file.addCacheBuster();
    });
  },

  // Return the WatchSet for this target's dependency info.
  getWatchSet: function () {
    var self = this;
    return self.watchSet;
  },

  getPluginProviderPackageDirs: function () {
    var self = this;
    return self.pluginProviderPackageDirs;
  },

  // Return the most inclusive architecture with which this target is
  // compatible. For example, if we set out to build a
  // 'os.linux.x86_64' version of this target (by passing that as
  // the 'arch' argument to the constructor), but ended up not
  // including anything that was specific to Linux, the return value
  // would be 'os'.
  mostCompatibleArch: function () {
    var self = this;
    return archinfo.leastSpecificDescription(_.pluck(self.slices, 'arch'));
  }
});


//////////////////// ClientTarget ////////////////////

var ClientTarget = function (options) {
  var self = this;
  Target.apply(this, arguments);

  // CSS files. List of File. They will be loaded at page load in the
  // order given.
  self.css = [];

  // List of segments of additional HTML for <head>/<body>.
  self.head = [];
  self.body = [];

  if (! archinfo.matches(self.arch, "browser"))
    throw new Error("ClientTarget targeting something that isn't a browser?");
};

inherits(ClientTarget, Target);

_.extend(ClientTarget.prototype, {
  // Minify the CSS in this target
  minifyCss: function (minifiers) {
    var self = this;

    // after concatenation some @import's might be left in the middle of CSS
    // file but they required to be in the beginning.
    var importsAst = {
      type: 'stylesheet',
      stylesheet: { rules: [] }
    };

    var cssAsts = _.map(self.css, function (file) {
      try {
        var ast = minifiers.CssParse(file.contents('utf8'));
      } catch (e) {
        throw new Error(util.format('Parsing %s: %s', file.url, e.message));
      }

      function isImportRule (node) {
{'fraction_non_alphanumeric': 0.1291372859404231, 'fraction_numerical': 0.0015110087782414735, 'mean_word_length': 2.3580791301721535, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2592743', 'n_tokens_mistral': 15734, 'n_tokens_neox': 15331, 'n_words': 6221}