From 99c90a64dd3ab088bc6174484b1a6ce90b418af6 Mon Sep 17 00:00:00 2001 From: Andy Armstrong <andya@edx.org> Date: Wed, 27 Jan 2016 19:19:31 -0500 Subject: [PATCH] Support compiling SASS for only one system --- Gemfile | 1 - Gemfile.lock | 3 +- circle.yml | 1 + pavelib/acceptance_test.py | 2 +- pavelib/assets.py | 117 ++++++++++++++++++++-------- pavelib/paver_tests/test_assets.py | 58 ++++++++++++++ pavelib/paver_tests/test_servers.py | 42 ++++++---- pavelib/prereqs.py | 1 + 8 files changed, 174 insertions(+), 51 deletions(-) create mode 100644 pavelib/paver_tests/test_assets.py diff --git a/Gemfile b/Gemfile index cef97fbc1ba..b438bc89e3e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,3 @@ source 'https://rubygems.org' -gem 'sass', '3.3.5' gem 'bourbon', '~> 4.0.2' gem 'neat', '~> 1.6.0' diff --git a/Gemfile.lock b/Gemfile.lock index 551990d98f6..fb1a1e69f7e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,7 +7,7 @@ GEM neat (1.6.0) bourbon (>= 3.1) sass (>= 3.3) - sass (3.3.5) + sass (3.4.21) thor (0.19.1) PLATFORMS @@ -16,4 +16,3 @@ PLATFORMS DEPENDENCIES bourbon (~> 4.0.2) neat (~> 1.6.0) - sass (= 3.3.5) diff --git a/circle.yml b/circle.yml index c37428a7394..95549be641a 100644 --- a/circle.yml +++ b/circle.yml @@ -29,6 +29,7 @@ dependencies: # Install a version which falls within that range. - pip install --exists-action w pbr==0.9.0 - pip install --exists-action w -r requirements/edx/base.txt + - pip install --exists-action w -r requirements/edx/paver.txt - if [ -e requirements/edx/post.txt ]; then pip install --exists-action w -r requirements/edx/post.txt ; fi - pip install coveralls==1.0 diff --git a/pavelib/acceptance_test.py b/pavelib/acceptance_test.py index 373db696edc..254af313bdd 100644 --- a/pavelib/acceptance_test.py +++ b/pavelib/acceptance_test.py @@ -30,7 +30,7 @@ __test__ = False # do not collect ]) def test_acceptance(options): """ - Run the acceptance tests for the either lms or cms + Run the acceptance tests for either lms or cms """ opts = { 'fasttest': getattr(options, 'fasttest', False), diff --git a/pavelib/assets.py b/pavelib/assets.py index e1d3cd15d75..d7c8c13c0da 100644 --- a/pavelib/assets.py +++ b/pavelib/assets.py @@ -12,24 +12,28 @@ from paver import tasks from paver.easy import sh, path, task, cmdopts, needs, consume_args, call_task, no_help from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler -import sass from .utils.envs import Env from .utils.cmd import cmd, django_cmd # setup baseline paths +ALL_SYSTEMS = ['lms', 'studio'] COFFEE_DIRS = ['lms', 'cms', 'common'] # A list of directories. Each will be paired with a sibling /css directory. -SASS_DIRS = [ +COMMON_SASS_DIRECTORIES = [ + path("common/static/sass"), +] +LMS_SASS_DIRECTORIES = [ path("lms/static/sass"), path("lms/static/themed_sass"), - path("cms/static/sass"), - path("common/static/sass"), path("lms/static/certificates/sass"), ] +CMS_SASS_DIRECTORIES = [ + path("cms/static/sass"), +] +THEME_SASS_DIRECTORIES = [] SASS_LOAD_PATHS = ['common/static', 'common/static/sass'] -SASS_CACHE_PATH = '/tmp/sass-cache' def configure_paths(): @@ -44,7 +48,7 @@ def configure_paths(): css_dir = theme_root / "static" / "css" if sass_dir.isdir(): css_dir.mkdir_p() - SASS_DIRS.append(sass_dir) + THEME_SASS_DIRECTORIES.append(sass_dir) if edxapp_env.env_tokens.get("COMPREHENSIVE_THEME_DIR", ""): theme_dir = path(edxapp_env.env_tokens["COMPREHENSIVE_THEME_DIR"]) @@ -52,16 +56,39 @@ def configure_paths(): lms_css = theme_dir / "lms" / "static" / "css" if lms_sass.isdir(): lms_css.mkdir_p() - SASS_DIRS.append(lms_sass) + THEME_SASS_DIRECTORIES.append(lms_sass) cms_sass = theme_dir / "cms" / "static" / "sass" cms_css = theme_dir / "cms" / "static" / "css" if cms_sass.isdir(): cms_css.mkdir_p() - SASS_DIRS.append(cms_sass) + THEME_SASS_DIRECTORIES.append(cms_sass) configure_paths() +def applicable_sass_directories(systems=None): + """ + Determine the applicable set of SASS directories to be + compiled for the specified list of systems. + + Args: + systems: A list of systems (defaults to all) + + Returns: + A list of SASS directories to be compiled. + """ + if not systems: + systems = ALL_SYSTEMS + applicable_directories = [] + applicable_directories.extend(COMMON_SASS_DIRECTORIES) + if "lms" in systems: + applicable_directories.extend(LMS_SASS_DIRECTORIES) + if "studio" in systems or "cms" in systems: + applicable_directories.extend(CMS_SASS_DIRECTORIES) + applicable_directories.extend(THEME_SASS_DIRECTORIES) + return applicable_directories + + class CoffeeScriptWatcher(PatternMatchingEventHandler): """ Watches for coffeescript changes @@ -99,7 +126,7 @@ class SassWatcher(PatternMatchingEventHandler): """ register files with observer """ - for dirname in SASS_LOAD_PATHS + SASS_DIRS: + for dirname in SASS_LOAD_PATHS + applicable_sass_directories(): paths = [] if '*' in dirname: paths.extend(glob.glob(dirname)) @@ -185,6 +212,7 @@ def compile_coffeescript(*files): @task @no_help @cmdopts([ + ('system=', 's', 'The system to compile sass for (defaults to all)'), ('debug', 'd', 'Debug mode'), ('force', '', 'Force full compilation'), ]) @@ -192,8 +220,18 @@ def compile_sass(options): """ Compile Sass to CSS. """ - debug = options.get('debug') + # Note: import sass only when it is needed and not at the top of the file. + # This allows other paver commands to operate even without libsass being + # installed. In particular, this allows the install_prereqs command to be + # used to install the dependency. + import sass + + debug = options.get('debug') + force = options.get('force') + systems = getattr(options, 'system', ALL_SYSTEMS) + if isinstance(systems, basestring): + systems = systems.split(',') if debug: source_comments = True output_style = 'nested' @@ -202,22 +240,39 @@ def compile_sass(options): output_style = 'compressed' timing_info = [] - - for sass_dir in SASS_DIRS: + system_sass_directories = applicable_sass_directories(systems) + all_sass_directories = applicable_sass_directories() + dry_run = tasks.environment.dry_run + for sass_dir in system_sass_directories: start = datetime.now() css_dir = sass_dir.parent / "css" - sass.compile( - dirname=(sass_dir, css_dir), - include_paths=SASS_LOAD_PATHS + SASS_DIRS, - source_comments=source_comments, - output_style=output_style, - ) - duration = datetime.now() - start - timing_info.append((sass_dir, css_dir, duration)) + + if force: + if dry_run: + tasks.environment.info("rm -rf {css_dir}/*.css".format( + css_dir=css_dir, + )) + else: + sh("rm -rf {css_dir}/*.css".format(css_dir=css_dir)) + + if dry_run: + tasks.environment.info("libsass {sass_dir}".format( + sass_dir=sass_dir, + )) + else: + sass.compile( + dirname=(sass_dir, css_dir), + include_paths=SASS_LOAD_PATHS + all_sass_directories, + source_comments=source_comments, + output_style=output_style, + ) + duration = datetime.now() - start + timing_info.append((sass_dir, css_dir, duration)) print("\t\tFinished compiling Sass:") - for sass_dir, css_dir, duration in timing_info: - print(">> {} -> {} in {}s".format(sass_dir, css_dir, duration)) + if not dry_run: + for sass_dir, css_dir, duration in timing_info: + print(">> {} -> {} in {}s".format(sass_dir, css_dir, duration)) def compile_templated_sass(systems, settings): @@ -226,15 +281,15 @@ def compile_templated_sass(systems, settings): `systems` is a list of systems (e.g. 'lms' or 'studio' or both) `settings` is the Django settings module to use. """ - for sys in systems: - if sys == "studio": - sys = "cms" + for system in systems: + if system == "studio": + system = "cms" sh(django_cmd( - sys, settings, 'preprocess_assets', - '{sys}/static/sass/*.scss'.format(sys=sys), - '{sys}/static/themed_sass'.format(sys=sys) + system, settings, 'preprocess_assets', + '{system}/static/sass/*.scss'.format(system=system), + '{system}/static/themed_sass'.format(system=system) )) - print("\t\tFinished preprocessing {} assets.".format(sys)) + print("\t\tFinished preprocessing {} assets.".format(system)) def process_xmodule_assets(): @@ -310,7 +365,7 @@ def update_assets(args): """ parser = argparse.ArgumentParser(prog='paver update_assets') parser.add_argument( - 'system', type=str, nargs='*', default=['lms', 'studio'], + 'system', type=str, nargs='*', default=ALL_SYSTEMS, help="lms or studio", ) parser.add_argument( @@ -334,7 +389,7 @@ def update_assets(args): compile_templated_sass(args.system, args.settings) process_xmodule_assets() compile_coffeescript() - call_task('pavelib.assets.compile_sass', options={'debug': args.debug}) + call_task('pavelib.assets.compile_sass', options={'system': args.system, 'debug': args.debug}) if args.collect: collect_assets(args.system, args.settings) diff --git a/pavelib/paver_tests/test_assets.py b/pavelib/paver_tests/test_assets.py new file mode 100644 index 00000000000..b690b6d0fe8 --- /dev/null +++ b/pavelib/paver_tests/test_assets.py @@ -0,0 +1,58 @@ +"""Unit tests for the Paver asset tasks.""" + +import ddt +from paver.easy import call_task + +from .utils import PaverTestCase + + +@ddt.ddt +class TestPaverAssetTasks(PaverTestCase): + """ + Test the Paver asset tasks. + """ + @ddt.data( + [""], + ["--force"], + ["--debug"], + ["--system=lms"], + ["--system=lms --force"], + ["--system=studio"], + ["--system=studio --force"], + ["--system=lms,studio"], + ["--system=lms,studio --force"], + ) + @ddt.unpack + def test_compile_sass(self, options): + """ + Test the "compile_sass" task. + """ + parameters = options.split(" ") + system = [] + if "--system=studio" not in parameters: + system += ["lms"] + if "--system=lms" not in parameters: + system += ["studio"] + debug = "--debug" in parameters + force = "--force" in parameters + self.reset_task_messages() + call_task('pavelib.assets.compile_sass', options={"system": system, "debug": debug, "force": force}) + expected_messages = [] + if force: + expected_messages.append("rm -rf common/static/css/*.css") + expected_messages.append("libsass common/static/sass") + if "lms" in system: + if force: + expected_messages.append("rm -rf lms/static/css/*.css") + expected_messages.append("libsass lms/static/sass") + if force: + expected_messages.append("rm -rf lms/static/css/*.css") + expected_messages.append("libsass lms/static/themed_sass") + if force: + expected_messages.append("rm -rf lms/static/certificates/css/*.css") + expected_messages.append("libsass lms/static/certificates/sass") + if "studio" in system: + if force: + expected_messages.append("rm -rf cms/static/css/*.css") + expected_messages.append("libsass cms/static/sass") + self.assertEquals(self.task_messages, expected_messages) diff --git a/pavelib/paver_tests/test_servers.py b/pavelib/paver_tests/test_servers.py index 9d75e57ce27..d2169b7ac07 100644 --- a/pavelib/paver_tests/test_servers.py +++ b/pavelib/paver_tests/test_servers.py @@ -11,21 +11,19 @@ EXPECTED_COFFEE_COMMAND = ( "{platform_root}/cms {platform_root}/common -type f -name \"*.coffee\"`" ) EXPECTED_SASS_COMMAND = ( - "sass --update --cache-location /tmp/sass-cache --default-encoding utf-8 --style compressed" - " --quiet" - " --load-path ." - " --load-path common/static" - " --load-path common/static/sass" - " --load-path lms/static/sass" - " --load-path lms/static/themed_sass" - " --load-path cms/static/sass --load-path common/static/sass" - " --load-path lms/static/certificates/sass" - " lms/static/sass:lms/static/css" - " lms/static/themed_sass:lms/static/css" - " cms/static/sass:cms/static/css" - " common/static/sass:common/static/css" - " lms/static/certificates/sass:lms/static/certificates/css" + "libsass {sass_directory}" ) +EXPECTED_COMMON_SASS_DIRECTORIES = [ + "common/static/sass", +] +EXPECTED_LMS_SASS_DIRECTORIES = [ + "lms/static/sass", + "lms/static/themed_sass", + "lms/static/certificates/sass", +] +EXPECTED_CMS_SASS_DIRECTORIES = [ + "cms/static/sass", +] EXPECTED_PREPROCESS_ASSETS_COMMAND = ( "python manage.py {system} --settings={asset_settings} preprocess_assets" " {system}/static/sass/*.scss {system}/static/themed_sass" @@ -236,7 +234,7 @@ class TestPaverServerTasks(PaverTestCase): )) expected_messages.append("xmodule_assets common/static/xmodule") expected_messages.append(EXPECTED_COFFEE_COMMAND.format(platform_root=platform_root)) - expected_messages.append(EXPECTED_SASS_COMMAND) + expected_messages.extend(self.expected_sass_commands(system=system)) if expected_collect_static: expected_messages.append(EXPECTED_COLLECT_STATIC_COMMAND.format( system=system, asset_settings=expected_asset_settings @@ -278,7 +276,7 @@ class TestPaverServerTasks(PaverTestCase): )) expected_messages.append("xmodule_assets common/static/xmodule") expected_messages.append(EXPECTED_COFFEE_COMMAND.format(platform_root=platform_root)) - expected_messages.append(EXPECTED_SASS_COMMAND) + expected_messages.extend(self.expected_sass_commands()) if expected_collect_static: expected_messages.append(EXPECTED_COLLECT_STATIC_COMMAND.format( system="lms", asset_settings=expected_asset_settings @@ -302,3 +300,15 @@ class TestPaverServerTasks(PaverTestCase): ) expected_messages.append(EXPECTED_CELERY_COMMAND.format(settings="dev_with_worker")) self.assertEquals(self.task_messages, expected_messages) + + def expected_sass_commands(self, system=None): + """ + Returns the expected SASS commands for the specified system. + """ + expected_sass_directories = [] + expected_sass_directories.extend(EXPECTED_COMMON_SASS_DIRECTORIES) + if system != 'cms': + expected_sass_directories.extend(EXPECTED_LMS_SASS_DIRECTORIES) + if system != 'lms': + expected_sass_directories.extend(EXPECTED_CMS_SASS_DIRECTORIES) + return [EXPECTED_SASS_COMMAND.format(sass_directory=directory) for directory in expected_sass_directories] diff --git a/pavelib/prereqs.py b/pavelib/prereqs.py index 0646ca30761..1136865381c 100644 --- a/pavelib/prereqs.py +++ b/pavelib/prereqs.py @@ -24,6 +24,7 @@ PYTHON_REQ_FILES = [ 'requirements/edx/github.txt', 'requirements/edx/local.txt', 'requirements/edx/base.txt', + 'requirements/edx/paver.txt', 'requirements/edx/post.txt', ] -- GitLab